[Go 教程系列筆記]結構而不是類-Go中的OOP

Go 面向對象?

Go 不是純粹的面向對象編程語言。摘自Go的常見問題解答,回答了Go是否面向對象的問題。

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).  

在接下來的教程中,我們將討論如何使用 Go實現面向對象的編程概念。與其他面向對象的語言(如Java)相比,它們中的一些在實現上有很大不同。

<!-- more -->

結構而不是類

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包。Employee結構在第 7行號中聲明。在第 14 行將一個名爲的方法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.go的第 12 行結構體 Employee 調用方法 LeavesRemaining()

此程序無法在 playground 上運行,因爲它有自定義程序包。如果你在本地運行這個程序,可以在 workspacepath/bin/oop 文件夾下運行 go install oop。這個命令將打印輸出:

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中的一個約定是命名一個函數 NewT(parameters),它創建一個類型T。這將像一個構造函數。如果包只定義了一個類型,那麼Go中的一個約定就是命名這個函數New(parameters)而不是NewT(parameters)。

讓我們對我們編寫的程序進行更改,以便每次創建員工時都可以使用。

第一步是取消導出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 struct 的起始字母改爲小寫 e,即我們已將 type Employee struct更改爲type employee struct。通過這樣做,我們已成功取消導出 employee 結構並阻止從其他包訪問。除非特定需要導出它們,否則將未導出結構的所有字段都取消導出是一種很好的做法。由於我們不需要employee包外的任何結構的字段,因此我們也取消了所有字段的輸出。

我們已經更改了在LeavesRemaining()方法中相應地字段名稱。

現在,由於employee未導出,因此無法從其他包創建Employee類型的值。因此我們在第14行 New() 提供了一個導出函數。將所需參數作爲輸入並返回新創建的員工。

該程序仍然需要進行更改以使其工作,但是讓我們運行它來瞭解到目前爲止更改的效果。如果運行此程序,它將失敗並出現以下編譯錯誤,

go/src/constructor/main.go:6: undefined: employee.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.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)替換構造函數的位置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章