Go 系列教程 —— 26. 結構體取代類

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。歡迎來到 [Golang 系列教程](https://studygolang.com/subject/2)的第 26 篇。## Go 支援物件導向嗎?Go 並不是完全物件導向的程式設計語言。Go 官網的 [FAQ](https://golang.org/doc/faq#Is_Go_an_object-oriented_language) 回答了 Go 是否是物件導向語言,摘錄如下。> 可以說是,也可以說不是。雖然 Go 有類型和方法,支援物件導向的編程風格,但卻沒有類型的階層。Go 中的“介面”概念提供了一種不同的方法,我們認為它便於使用,也更為普遍。Go 也可以將結構體嵌套使用,這與子類化(Subclassing)類似,但並不完全相同。此外,Go 提供的特性比 C++ 或 Java 更為通用:子類可以由任何類型的資料來定義,甚至是內建類型(如簡單的“未裝箱的”整型)。這在結構體(類)中沒有受到限制。在接下來的教程裡,我們會討論如何使用 Go 來實現物件導向編程概念。與其它物件導向語言(如 Java)相比,Go 有很多完全不同的特性。## 使用結構體,而非類Go 不支援類,而是提供了[結構體](https://studygolang.com/articles/12263)。結構體中可以添加[方法](https://studygolang.com/articles/12264)。這樣可以將資料和操作資料的方法綁定在一起,實現與類相似的效果。為了加深理解,我們來編寫一個樣本吧。在樣本中,我們建立一個自訂[包](https://studygolang.com/articles/11893),它協助我們更好地理解,結構體是如何有效地取代類的。在你的 Go 工作區建立一個名為 `oop` 的檔案夾。在 `opp` 中再建立子檔案夾 `employee`。在 `employee` 內,建立一個名為 `employee.go` 的檔案。檔案夾結構會是這樣:```workspacepath -> oop -> employee -> employee.go```請將 `employee.go` 裡的內容替換為如下所示的代碼。```gopackage employeeimport ( "fmt")type Employee struct { FirstName stringLastName stringTotalLeaves intLeavesTaken int}func (e Employee) LeavesRemaining() { fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))}```在上述程式裡,第 1 行指定了該檔案屬於 `employee` 包。而第 7 行聲明了一個 `Employee` 結構體。在第 14 行,結構體 `Employee` 添加了一個名為 `LeavesRemaining` 的方法。該方法會計算和顯示員工的剩餘休假數。於是現在我們有了一個結構體,並綁定了結構體的方法,這與類很相似。接著在 `oop` 檔案夾裡建立一個檔案,命名為 `main.go`。現在目錄結構如下所示: ```workspacepath -> oop -> employee -> employee.go workspacepath -> oop -> main.go ````main.go` 的內容如下所示:```gopackage mainimport "oop/employee"func main() { e := employee.Employee {FirstName: "Sam",LastName: "Adolf",TotalLeaves: 30,LeavesTaken: 20,}e.LeavesRemaining()}```我們在第 3 行引用了 `employee` 包。在 `main()`(第 12 行),我們調用了 `Employee` 的 `LeavesRemaining()` 方法。由於有自訂包,這個程式不能在 go playground 上運行。你可以在你的本地運行,在 `workspacepath/bin/oop` 下輸入命令 `go install opp`,程式會列印輸出:```bashSam Adolf has 10 leaves remaining ```## 使用 New() 函數,而非構造器我們上面寫的程式看起來沒什麼問題,但還是有一些細節問題需要注意。我們看看當定義一個零值的 `employee` 結構體變數時,會發生什麼。將 `main.go` 的內容修改為如下代碼:```gopackage mainimport "oop/employee"func main() { var e employee.Employeee.LeavesRemaining()}```我們的修改只是建立一個零值的 `Employee` 結構體變數(第 6 行)。該程式會輸出:```bashhas 0 leaves remaining```你可以看到,使用 `Employee` 建立的零值變數沒有什麼用。它沒有合法的姓名,也沒有合理的休假細節。在像 Java 這樣的 OOP 語言中,是使用構造器來解決這種問題的。一個合法的對象必須使用參數化的構造器來建立。Go 並不支援構造器。如果某類型的零值不可用,需要程式員來隱藏該類型,避免從其他包直接存取。程式員應該提供一種名為 `NewT(parameters)` 的 [函數](https://studygolang.com/articles/11892),按照要求來初始化 `T` 類型的變數。按照 Go 的慣例,應該把建立 `T` 類型變數的函數命名為 `NewT(parameters)`。這就類似於構造器了。如果一個包只含有一種類型,按照 Go 的慣例,應該把函數命名為 `New(parameters)`, 而不是 `NewT(parameters)`。讓我修改一下原先的代碼,使得每當建立 `employee` 的時候,它都是可用的。首先應該讓 `Employee` 結構體不可引用,然後建立一個 `New` 函數,用於建立 `Employee` 結構體變數。在 `employee.go` 中輸入下面代碼: ```gopackage employeeimport ( "fmt")type employee struct { firstName stringlastName stringtotalLeaves intleavesTaken 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`,也就是將 `type Employee struct` 改為了 `type employee struct`。通過這種方法,我們把 `employee` 結構體變為了不可引用的,防止其他包對它的訪問。除非有特殊需求,否則也要隱藏所有不可引用的結構體的所有欄位,這是 Go 的最佳實務。由於我們不會在外部包需要 `employee` 的欄位,因此我們也讓這些欄位無法引用。同樣,我們還修改了 `LeavesRemaining()` 的方法。現在由於 `employee` 不可引用,因此不能在其他包內直接建立 `Employee` 類型的變數。於是我們在第 14 行提供了一個可引用的 `New` 函數,該函數接收必要的參數,返回一個新建立的 `employee` 結構體變數。這個程式還需要一些必要的修改,但現在先運行這個程式,理解一下當前的修改。如果運行當前程式,編譯器會報錯,如下所示:```bashgo/src/constructor/main.go:6: undefined: employee.Employee ```這是因為我們將 `Employee` 設定為不可引用,因此編譯器會報錯,提示該類型沒有在 `main.go` 中定義。很完美,正如我們期望的一樣,其他包現在不能輕易建立零值的 `employee` 變數了。我們成功地避免了建立停用 `employee` 結構體變數。現在建立 `employee` 變數的唯一方法就是使用 `New` 函數。如下所示,修改 `main.go` 裡的內容。```gopackage main import "oop/employee"func main() { e := employee.New("Sam", "Adolf", 30, 20)e.LeavesRemaining()}```該檔案唯一的修改就是第 6 行。通過向 `New` 函數傳入所需變數,我們建立了一個新的 `employee` 結構體變數。下面是修改後的兩個檔案的內容。employee.go```gopackage employeeimport ( "fmt")type employee struct { firstName stringlastName stringtotalLeaves intleavesTaken 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```gopackage main import "oop/employee"func main() { e := employee.New("Sam", "Adolf", 30, 20)e.LeavesRemaining()}```運行該程式,會輸出:```bashSam Adolf has 10 leaves remaining ```現在你能明白了,雖然 Go 不支援類,但結構體能夠很好地取代類,而以 `New(parameters)` 簽名的方法可以替代構造器。關於 Go 中的類和構造器到此結束。祝你愉快。**上一教程 - [Mutex](https://studygolang.com/articles/12598)****下一教程 - [組合取代繼承](https://studygolang.com/articles/12680)**

via: https://golangbot.com/structs-instead-of-classes/

作者:Nick Coghlan 譯者:Noluye 校對:polaris1119

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

2066 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.