[譯] part 26: golang 的物件導向

咔嘰咔嘰發表於2019-04-11

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的建立變數是不可用的。它沒有有效的FirstNameLastName,也沒有有效的休假詳情。

在像 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()方法去替代建構函式來有效地實現了類似的行為。

相關文章