- 原文地址:Part 26: Structs Instead of Classes - OOP in Go
- 原文作者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
Go 是物件導向的語言嗎
Go 不是純粹的物件導向的程式語言。摘自 Go 的常見問題解答,回答了 Go 是否物件導向的問題。
是也不是。儘管 Go 具有型別和方法,並且允許物件導向的程式設計風格,但是沒有型別層次結構。 Go 中 “interface” 的概念提供了一種我們認為易於使用且在某些方面更為通用的方法。還有一些方法可以將型別嵌入到其他型別中,以提供類似但不完全相同的子類化。此外,Go 中的方法比 C ++或 Java 更通用:可以為任何型別的資料定義它們,甚至是內建型別,例如普通的整數。它們不限於結構(類)。
在即將到來的教程中,我們將討論如何使用 Go 實現物件導向的程式設計概念。與其他物件導向的語言(如 Java)相比,它們中的一些在實現上有很大不同。
結構取代類
Go 沒有提供類,但它提供了結構,可以在結構上新增方法。這提供了將資料和方法捆綁在一起的行為,類似於類。
讓我們馬上開始一個例子,以便更好地理解。
我們將在此示例中建立一個自定義包,因為它有助於更好地理解結構如何有效地替代類。
在 Go 工作區內建立一個資料夾,並將其命名為 oop。在 oop 中建立一個子資料夾 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))
}
複製程式碼
在上面的程式中,第一行指定此檔案屬於 employee 包。第 7 行宣告瞭Employee
結構,一個名為LeavesRemaining
的方法被新增到Employee
結構中,該方法計算並顯示員工剩餘的假期。現在我們有一個結構和一個方法,它執行在一個類似於類的結構上。
在 oop 資料夾下建立一個 main.go 檔案。
現在檔案的結構如下,
workspacepath -> oop -> employee -> employee.go
workspacepath -> oop -> main.go
複製程式碼
main.go 的內容如下,
package main
import "oop/employee"
func main() {
e := employee.Employee {
FirstName: "Sam",
LastName: "Adolf",
TotalLeaves: 30,
LeavesTaken: 20,
}
e.LeavesRemaining()
}
複製程式碼
我們在第 3 行匯入了 employee
包,在 main()
函式中呼叫了 Employee
結構的LeavesRemaining()
方法。
此程式無法在 playground 上執行,因為它具有自定義包。如果你在本地執行這個程式,通過令go install oop
後跟workspacepath/bin/oop
,該程式將列印輸出,
Sam Adolf has 10 leaves remaining
複製程式碼
New()
函式取代建構函式
我們上面寫的程式看起來不錯,但它有一個小問題。讓我們看看當我們定義零值的Employee
結構時會發生什麼。將 main.go 的內容更改為以下程式碼,
package main
import "oop/employee"
func main() {
var e employee.Employee
e.LeavesRemaining()
}
複製程式碼
我們做的唯一修改是在第 6 行建立了零值Employee
。該程式將輸出,
has 0 leaves remaining
複製程式碼
如你所見,使用零值Employee
的建立變數是不可用的。它沒有有效的FirstName
,LastName
,也沒有有效的休假詳情。
在像 Java 這樣的 OOP 語言中,這個問題可以通過建構函式來解決。可以使用引數化建構函式建立有效物件。
Go 不支援建構函式。如果型別的零值不可用,則需要程式設計師讓型別不能匯出使之不能從其他包訪問,並且還要提供名為NewT()
的函式,該函式使用所需的值初始化型別T
。Go 中的一個約定是命名一個函式,它為NewT()
建立一個 T 型別的值。這就像一個建構函式。如果包只定義了一種型別,那麼 Go 中的一個約定就是將此函式命名為New()
而不是NewT()
。
讓我們修改一下程式,以便每次建立員工時都可以使用。
第一步是讓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
設為小寫,即我們已將型別Employee
結構更改為型別employee
結構。通過這樣做,我們能讓employee
結構不能被匯出,從而阻止了其他包的訪問。除非需要指定匯出它們,否則將不能匯出的結構的所有欄位都禁止匯出是一種很好的做法。由於我們不需要在包外的任何地方使用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()
方法去替代建構函式來有效地實現了類似的行為。