歡迎來到 Golang 系列教程[1]的第 26 篇。
Go 支持面向對象嗎?Go 並不是完全面向對象的程式語言。Go 官網的 FAQ[2] 回答了 Go 是否是面向對象語言,摘錄如下。
可以說是,也可以說不是。雖然 Go 有類型和方法,支持面向對象的編程風格,但卻沒有類型的層次結構。Go 中的「接口」概念提供了一種不同的方法,我們認為它易於使用,也更為普遍。Go 也可以將結構體嵌套使用,這與子類化(Subclassing)類似,但並不完全相同。此外,Go 提供的特性比 C++ 或 Java 更為通用:子類可以由任何類型的數據來定義,甚至是內建類型(如簡單的「未裝箱的」整型)。這在結構體(類)中沒有受到限制。
在接下來的教程裡,我們會討論如何使用 Go 來實現面向對象編程概念。與其它面向對象語言(如 Java)相比,Go 有很多完全不同的特性。
使用結構體,而非類Go 不支持類,而是提供了結構體。結構體中可以添加方法。這樣可以將數據和操作數據的方法綁定在一起,實現與類相似的效果。
為了加深理解,我們來編寫一個示例吧。
在示例中,我們創建一個自定義包,它幫助我們更好地理解,結構體是如何有效地取代類的。
在你的 Go 工作區創建一個名為 oop 的文件夾。在 opp 中再創建子文件夾 employee。在 employee 內,創建一個名為 employee.go 的文件。
文件夾結構會是這樣:
workspacepath -> oop -> employee -> employee.go請將 employee.go 裡的內容替換為如下所示的代碼。
package employee
import (
"fmt"
)
type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
}
func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}在上述程序裡,第 1 行指定了該文件屬於 employee 包。而第 7 行聲明了一個 Employee 結構體。在第 14 行,結構體 Employee 添加了一個名為 LeavesRemaining 的方法。該方法會計算和顯示員工的剩餘休假數。於是現在我們有了一個結構體,並綁定了結構體的方法,這與類很相似。
接著在 oop 文件夾裡創建一個文件,命名為 main.go。
現在目錄結構如下所示:
workspacepath -> oop -> employee -> employee.go
workspacepath -> oop -> main.gomain.go 的內容如下所示:
package main
import "oop/employee"
func main() {
e := employee.Employee {
FirstName: "Sam",
LastName: "Adolf",
TotalLeaves: 30,
LeavesTaken: 20,
}
e.LeavesRemaining()
}我們在第 3 行引用了 employee 包。在 main()(第 12 行),我們調用了 Employee 的 LeavesRemaining() 方法。
由於有自定義包,這個程序不能在 go playground 上運行。你可以在你的本地運行,在 workspacepath/bin/oop 下輸入命令 go install opp,程序會列印輸出:
Sam Adolf has 10 leaves remaining
使用 New() 函數,而非構造器我們上面寫的程序看起來沒什麼問題,但還是有一些細節問題需要注意。我們看看當定義一個零值的 employee 結構體變量時,會發生什麼。將 main.go 的內容修改為如下代碼:
package main
import "oop/employee"
func main() {
var e employee.Employee
e.LeavesRemaining()
}我們的修改只是創建一個零值的 Employee 結構體變量(第 6 行)。該程序會輸出:
has 0 leaves remaining你可以看到,使用 Employee 創建的零值變量沒有什麼用。它沒有合法的姓名,也沒有合理的休假細節。
在像 Java 這樣的 OOP 語言中,是使用構造器來解決這種問題的。一個合法的對象必須使用參數化的構造器來創建。
Go 並不支持構造器。如果某類型的零值不可用,需要程式設計師來隱藏該類型,避免從其他包直接訪問。程式設計師應該提供一種名為 NewT(parameters) 的函數,按照要求來初始化 T 類型的變量。按照 Go 的慣例,應該把創建 T 類型變量的函數命名為 NewT(parameters)。這就類似於構造器了。如果一個包只含有一種類型,按照 Go 的慣例,應該把函數命名為 New(parameters), 而不是 NewT(parameters)。
讓我修改一下原先的代碼,使得每當創建 employee 的時候,它都是可用的。
首先應該讓 Employee 結構體不可引用,然後創建一個 New 函數,用於創建 Employee 結構體變量。在 employee.go 中輸入下面代碼:
package employee
import (
"fmt"
)
type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
}
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {firstName, lastName, totalLeave, leavesTaken}
return e
}
func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}我們進行了一些重要的修改。我們把 Employee 結構體的首字母改為小寫 e,也就是將 type Employee struct 改為了 type employee struct。通過這種方法,我們把 employee 結構體變為了不可引用的,防止其他包對它的訪問。除非有特殊需求,否則也要隱藏所有不可引用的結構體的所有欄位,這是 Go 的最佳實踐。由於我們不會在外部包需要 employee 的欄位,因此我們也讓這些欄位無法引用。
同樣,我們還修改了 LeavesRemaining() 的方法。
現在由於 employee 不可引用,因此不能在其他包內直接創建 Employee 類型的變量。於是我們在第 14 行提供了一個可引用的 New 函數,該函數接收必要的參數,返回一個新創建的 employee 結構體變量。
這個程序還需要一些必要的修改,但現在先運行這個程序,理解一下當前的修改。如果運行當前程序,編譯器會報錯,如下所示:
go/src/constructor/main.go:6: undefined: employee.Employee這是因為我們將 Employee 設置為不可引用,因此編譯器會報錯,提示該類型沒有在 main.go 中定義。很完美,正如我們期望的一樣,其他包現在不能輕易創建零值的 employee 變量了。我們成功地避免了創建不可用的 employee 結構體變量。現在創建 employee 變量的唯一方法就是使用 New 函數。
如下所示,修改 main.go 裡的內容。
package main
import "oop/employee"
func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}該文件唯一的修改就是第 6 行。通過向 New 函數傳入所需變量,我們創建了一個新的 employee 結構體變量。
下面是修改後的兩個文件的內容。
employee.go
package employee
import (
"fmt"
)
type employee struct {
firstName string
lastName string
totalLeaves int
leavesTaken int
}
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
e := employee {firstName, lastName, totalLeave, leavesTaken}
return e
}
func (e employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}main.go
package main
import "oop/employee"
func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}運行該程序,會輸出:
Sam Adolf has 10 leaves remaining現在你能明白了,雖然 Go 不支持類,但結構體能夠很好地取代類,而以 New(parameters) 籤名的方法可以替代構造器。
關於 Go 中的類和構造器到此結束。祝你愉快。
上一教程 - Mutex
下一教程 - 組合取代繼承[3]
via: https://golangbot.com/structs-instead-of-classes/
作者:Nick Coghlan[4]譯者:Noluye[5]校對:polaris1119[6]
本文由 GCTT[7] 原創編譯,Go 中文網[8] 榮譽推出
參考資料[1]Golang 系列教程: https://studygolang.com/subject/2
[2]FAQ: https://golang.org/doc/faq#Is_Go_an_object-oriented_language
[3]組合取代繼承: https://studygolang.com/articles/12680
[4]Nick Coghlan: https://golangbot.com/about/
[5]Noluye: https://github.com/Noluye
[6]polaris1119: https://github.com/polaris1119
[7]GCTT: https://github.com/studygolang/GCTT
[8]Go 中文網: https://studygolang.com/