- 原文地址:Part 26: Structs Instead of Classes - OOP in Go
- 原文做者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
Go 不是純粹的面向對象的編程語言。摘自 Go 的常見問題解答,回答了 Go 是否面向對象的問題。程序員
是也不是。儘管 Go 具備類型和方法,而且容許面向對象的編程風格,可是沒有類型層次結構。 Go 中 「interface」 的概念提供了一種咱們認爲易於使用且在某些方面更爲通用的方法。還有一些方法能夠將類型嵌入到其餘類型中,以提供相似但不徹底相同的子類化。此外,Go 中的方法比 C ++或 Java 更通用:能夠爲任何類型的數據定義它們,甚至是內置類型,例如普通的整數。它們不限於結構(類)。golang
在即將到來的教程中,咱們將討論如何使用 Go 實現面向對象的編程概念。與其餘面向對象的語言(如 Java)相比,它們中的一些在實現上有很大不一樣。編程
Go 沒有提供類,但它提供告終構,能夠在結構上添加方法。這提供了將數據和方法捆綁在一塊兒的行爲,相似於類。編程語言
讓咱們立刻開始一個例子,以便更好地理解。函數
咱們將在此示例中建立一個自定義包,由於它有助於更好地理解結構如何有效地替代類。oop
在 Go 工做區內建立一個文件夾,並將其命名爲 oop。在 oop 中建立一個子文件夾 employee。在 employee 文件夾中,建立一個名爲 employee.go 的文件spa
結構看起來像這樣,code
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()
方法去替代構造函數來有效地實現了相似的行爲。