Go 經典入門系列 26:結構體取代類​

2021-02-17 Go語言中文網

歡迎來到 Golang 系列教程[1]的第 26 篇。

Go 支持面向對象嗎?

Go 並不是完全面向對象的程式語言。Go 官網的 FAQ[2] 回答了 Go 是否是面向對象語言,摘錄如下。

可以說是,也可以說不是。雖然 Go 有類型和方法,支持面向對象的編程風格,但卻沒有類型的層次結構。Go 中的「接口」概念提供了一種不同的方法,我們認為它易於使用,也更為普遍。Go 也可以將結構體嵌套使用,這與子類化(Subclassing)類似,但並不完全相同。此外,Go 提供的特性比 C++ 或 Java 更為通用:子類可以由任何類型的數據來定義,甚至是內建類型(如簡單的「未裝箱的」整型)。這在結構體(類)中沒有受到限制。

在接下來的教程裡,我們會討論如何使用 Go 來實現面向對象編程概念。與其它面向對象語言(如 Java)相比,Go 有很多完全不同的特性。

使用結構體,而非類

Go 不支持類,而是提供了結構體。結構體中可以添加方法。這樣可以將數據和操作數據的方法綁定在一起,實現與類相似的效果。

為了加深理解,我們來編寫一個示例吧。

在示例中,我們創建一個自定義包,它幫助我們更好地理解,結構體是如何有效地取代類的。

在你的 Go 工作區創建一個名為 oop 的文件夾。在 opp 中再創建子文件夾 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))
}

在上述程序裡,第 1 行指定了該文件屬於 employee 包。而第 7 行聲明了一個 Employee 結構體。在第 14 行,結構體 Employee 添加了一個名為 LeavesRemaining 的方法。該方法會計算和顯示員工的剩餘休假數。於是現在我們有了一個結構體,並綁定了結構體的方法,這與類很相似。

接著在 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()(第 12 行),我們調用了 Employee 的 LeavesRemaining() 方法。

由於有自定義包,這個程序不能在 go playground 上運行。你可以在你的本地運行,在 workspacepath/bin/oop 下輸入命令 go install opp,程序會列印輸出:

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 的慣例,應該把創建 T 類型變量的函數命名為 NewT(parameters)。這就類似於構造器了。如果一個包只含有一種類型,按照 Go 的慣例,應該把函數命名為 New(parameters), 而不是 NewT(parameters)。

讓我修改一下原先的代碼,使得每當創建 employee 的時候,它都是可用的。

首先應該讓 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,也就是將 type Employee struct 改為了 type employee struct。通過這種方法,我們把 employee 結構體變為了不可引用的,防止其他包對它的訪問。除非有特殊需求,否則也要隱藏所有不可引用的結構體的所有欄位,這是 Go 的最佳實踐。由於我們不會在外部包需要 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(parameters) 籤名的方法可以替代構造器。

關於 Go 中的類和構造器到此結束。祝你愉快。

上一教程 - Mutex

下一教程 - 組合取代繼承[3]

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

作者:Nick Coghlan[4]譯者:Noluye[5]校對:polaris1119[6]

本文由 GCTT[7] 原創編譯,Go 中文網[8] 榮譽推出

參考資料[1]

Golang 系列教程: https://studygolang.com/subject/2

[2]

FAQ: https://golang.org/doc/faq#Is_Go_an_object-oriented_language

[3]

組合取代繼承: https://studygolang.com/articles/12680

[4]

Nick Coghlan: https://golangbot.com/about/

[5]

Noluye: https://github.com/Noluye

[6]

polaris1119: https://github.com/polaris1119

[7]

GCTT: https://github.com/studygolang/GCTT

[8]

Go 中文網: https://studygolang.com/

相關焦點

  • Go 經典入門系列 28:Defer
    歡迎來到 Golang 系列教程[1]的第 29 篇。什麼是 defer? defer 語句的用途是:含有 defer 語句的函數,會在該函數將要返回之前,調用另一個函數。這個定義可能看起來很複雜,我們通過一個示例就很容易明白了。
  • Go 經典入門系列 30:錯誤處理
    歡迎來到 Golang 系列教程[1]的第 30 篇。什麼是錯誤? 錯誤表示程序中出現了異常情況。比如當我們試圖打開一個文件時,文件系統裡卻並沒有這個文件。這就是異常情況,它用一個錯誤來表示。斷言底層結構體類型,使用結構體欄位獲取更多信息如果你仔細閱讀了 `Open`[7] 函數的文檔,你可以看見它返回的錯誤類型是 *PathError。
  • 入門教程:花 5 分鐘學習 Go 語言
    原文如下:基本作為經典的 「Hello World」,你的第一個 Go 程序非常簡單:先為我們的項目創建 workspace:$ mkdir hello$ cd hello接著創建並初始化 Go module:$ go mod init hello使用你最喜歡的編輯器創建
  • Go 經典入門系列 24:Select
    歡迎來到 Golang 系列教程[1]的第 24 篇。什麼是 select? select 語句用於在多個發送/接收信道操作中進行選擇。select 語句會一直阻塞,直到發送/接收操作準備就緒。如果有多個信道操作準備完畢,select 會隨機地選取其中之一執行。
  • 為Java程式設計師準備的Go教程:快速入門
    引用變量集合類最容易理解,自己的類也可以,不過基本類型不行,基本類型不是引用類型的,他們在方法傳參的時候,是拷貝的值。結構體替代類Go中沒有類型的概念,只有結構體,這個和C是一樣的。func (p Person) GetName() string {    return p.name}這就是通過.操作符訪問變量的方式,同時它也是一個為結構體定義方法的例子,和函數不一樣的是,在func關鍵字後要執行該方法的接收者,這個方法就是屬於這個接收者,例子中是Person這個結構體。
  • C語言系列(九):結構體
    類型n 成員名n;};其中,struct是定義結構體類型的關鍵字;結構體類型名必須是合法的C標識符,與其前面的struct一起共同構成結構體類型名;花括號內的內容是結構體類型所包括的結構體成員,也稱為結構體分量。
  • go-mir v2.0.0 發布,用 Go 結構體標籤定義 handler 路由信息的...
    go-mir v2.0.0 發布了,推薦使用。
  • Go 數據存儲篇(七):GORM 使用入門
    2、GORM 使用示例使用之前需要先安裝 GORM:go get github.com/jinzhu/gorm然後我們編寫一段示例代碼:package mainimport (    "fmt"    "github.com/jinzhu/gorm"    _ "github.com/
  • Go 經典入門系列 32:panic 和 recover
    panic 和 recover歡迎來到 Golang 系列教程[1]的第 32 篇。什麼是 panic? 在 Go 語言中,程序中一般是使用錯誤[2]來處理異常情況。對於程序中出現的大部分異常情況,錯誤就已經夠用了。
  • 快速上手系列-C語言之結構體(二)結構體數組與結構體指針
    結構體數組對於結構體數組,我們先回想一下整型數組,然後舉例我們要統計咱們班30個人的姓名,學號 ,成績,如果我們用結構體變量來實現是不現實的。那麼我們就準備用結構體數組來完成這事。結構體數組就是同一類型的結構體變量的集合,內存分布上是連續的。
  • 超詳細go入門筆記
    切片可以追加元素,追加元素時容量增大,與數組相比切片不需要設定長度 4.切片數據結構可以理解為一個結構體,包含三個要素。[](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a0ca32a0c934898ba8ed26ccce28b9c~tplv-k3u1fbpfcp-zoom-1.image)#### 5.3 map* 5.3.1 map概念Map 是一種無序的鍵值對的集合。
  • Go語言進階之路(一):變量、類型、數組、切片、字典和結構體
    Go語言中除了這些基礎類型,還有數組、切片、字典、指針、結構體、通道、函數、接口這些類型,後續的文章會詳細講這些。二 變量Go語言定義變量使用var關鍵字。定義變量時可以選擇指定類型,或者讓編譯器自動推導出類型,可以指定初始化值,也可以使用編輯器的初始值。
  • Go 經典入門系列 10:switch 語句
    這是 Golang 系列教程 中的第 10 篇。switch 是一個條件語句,用於將表達式的值與可能匹配的選項列表進行比較,並根據匹配情況執行相應的代碼塊。它可以被認為是替代多個 if else 子句的常用方式。
  • C語言結構體常見寫法及用法
    關注+星標公眾號,不錯過精彩內容作者 | strongerHuang微信公眾號 | 嵌入式專欄C語言可謂是編程界的傳奇語言,歷經幾十年,依然遙遙領先。方法1:定義結構體stu,此時結構體相當於一個類型,比如int,如需使用此結構體,方法同int.
  • Go Gin 系列四:開發文章模塊
    /├── conf│   └── app.ini├── main.go├── middleware├── models│   ├── models.go│   └── tag.go├── pkg│   ├── e│   │   ├── code.go│   │   └── msg.go│   ├── setting│   │   └── setting.go│   └── util│   └
  • 結構體
    為了解決這個問題,C 語言中提供了一種組合數據類型「結構體」。結構體是一種組合數據類型,由用戶自己定義。結構體類型中的元素既可以是基本數據類型,也可以結構體類型。定義結構體變量在 C 語言中,定義結構體變量的方式有 3 種:第 1 種 先定義結構體類型,再定義結構體變量,一般形式為:struct 結構體名{成員列表};struct 結構體名 變量名;例如:struct Employee{char
  • 煎魚 Go Gin 系列四:開發文章模塊
    /├── conf│   └── app.ini├── main.go├── middleware├── models│   ├── models.go│   └── tag.go├── pkg│   ├── e│   │   ├── code.go│   │   └── msg.go│   ├── setting│   │   └── setting.go│   └── util│   └
  • ​Go 經典入門系列 33:函數是一等公民(頭等函數)
    first class functions歡迎來到 Golang 系列教程[1]的第 33 篇。什麼是頭等函數?運行該程序後會輸出:Welcome Gophers用戶自定義的函數類型 正如我們定義自己的結構體[7]類型一樣,我們可以定義自己的函數類型。
  • 剖析c語言結構體的高級用法(二)
    ,必須要有一個結構體成員來才行)寫成c語言程序空結構體的話,它會報錯,在新一點的編譯器裡面就不會報錯(比如dev,gcc)。這個就是我們接下來要討論的結構體對齊問題了。3、下面我們就來接著分析上面最後列印出結構體佔用內存大小為12個字節,卻不是9個字節大小的原因。首先我們要搞清楚好端端的結構體為啥要字節對齊呢?
  • 經典巧板系列:七巧板入門基礎問題004
    平面幾何是小學到初中階段都要學習的重要知識,七巧板則是經典的中國傳統智力遊戲,玩好七巧板有助於孩子學習平面幾何知識,對於培養數學的思維有也一定的幫助。從今天(9月16日)開始,我們推出《經典巧板系列:七巧板入門基礎問題》,該系列共40期,每期1-3個與七巧板相關的基礎問題,所有問題由益智遊戲方面的專家武元元老師提供。