Golang 學習路線 - Part 26:Go 中的 OOP

這裏是 Golang 教程系列的第二十六部分。

是面向對象嗎?

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 有類型和方法,並且允許面向對象的編程風格,但是沒有類型層次結構。Go 中的 `interface` 
概念提供了一種不同的方法,我們認爲這種方法易於使用,而且在某些方面更通用。還有一些方法可以將類型嵌入到其他類型中,
提供類似於(但並不相同的)子類一樣的東西。此外,Go 中的方法比 c++ 或 Java 中的方法更通用:它們可以爲任何類型的數據定義,
甚至像 plain 這樣的內置類型、`unboxed` 整型。它們不侷限於結構體(類)。

在下面的討論中我們將學習使用 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 包,Employee 結構體在第七行中聲明,第十四行中名爲 LeavesRemaining 的方法被添加到 Employee 結構中。這將計算並顯示員工剩餘的休假天數。現在,我們有了一個結構體和一個方法,該方法和捆綁在一起的結構體進行操作,類似於class

在 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()
}

我們在第三行導入了 employee 包,在 main 方法中的第二十行調用了 employee 結構體的 LeavesRemaining 方法。

這個程序運行的話將會打印:

Sam Adolf has 10 leaves remaining  

使用 new() 函數而不是用構造函數

我們在上面寫的例子看起來不錯,但是其中有一個微妙的問題。如果用零值定義 employee 結構時會發生什麼。將 main.go 更新爲以下代碼:

package main

import "oop/employee"

func main() {  
    var e employee.Employee
    e.LeavesRemaining()
}

我們做的唯一的修改就是在第六行創建了 Employee 並且是空的。該程序將輸出

has 0 leaves remaining

如你所見,使用零值(空值)創建的變量 Employee 不可用。它沒有有效的 FirstName,LastName,也沒有有效的詳細的 leave 信息。

在其他 OOP 語言(如 java)中,可以使用構造函數解決此問題。可以使用帶參構造函數來創建有效對象。

Go 不支持構造函數。如果類型的零值不可用,你的工作的就是取消這個引用,防止從其他包訪問,並提供一個名爲 NewT(parameters) 的函數,函數用所需的值初始化類型 T。在 Go 中命名約定是一個函數,該函數創建一個類型爲 T 的值 NewT(parameters) 的值。這就像一個構造函數。如果包僅定義了一種類型,那麼 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 結構的首字母改爲小寫,即已更改 type Employee structtype employee struct。這樣,我們已成功取消引用 employee 結構體並阻止了其他程序包的訪問。除非有特殊需要,否則最好將私有結構體的所有字段都可以被訪問(公開)。由於我們不需要 employee 包被引用任何字段,所以我們將所有字段都私有了。

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

由於 employee 被私有,所以不可能從其他包中創建 employee 類型的值。所以我們在第十四行提供了一個公有的函數。它接收所需的參數作爲輸入並返回一個新創建的 employee 。

這個程序仍然需要進行一些修改才能正常工作,但是我們現在運行這個程序來了解到目前爲止更改的效果。如果這個程序運行,它將失敗與以下編譯錯誤

go/src/constructor/main.go:6: undefined: employee.Employee  

這是因爲我們有私有的 Employee,因此編譯器會拋出錯誤,說明 main.go 中沒有定義這種類型。這正是我們想要的。現在沒有其他包能夠創建零值 Employee。我們成功地阻止了創建。現在創建 Employee 的惟一方法是使用 New 函數。

在 main.go 替換爲下面的代碼

package main  

import "oop/employee"

func main() {  
    e := employee.New("Sam", "Adolf", 30, 20)
    e.LeavesRemaining()
}

對 main.go 中唯一的修改就是在第六行,通過將所需的參數傳遞給 New 函數,這是我們創建了一個 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 不支持類,但可以有效地使用 struct 來代替類,而 signature New(parameters) 的方法可以代替構造函數。

這就是在 Go 中的類和構造函數。祝你有美好的一天。

下一個教程 - 用組合代替繼承

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