Go | 使用匿名欄位模擬繼承
💬 簡介
在 Go 語言中,結構(struct
)是一種非常靈活且常用的資料型別,結構可以包含一個或多個匿名欄位。這些匿名欄位不會顯示欄位名稱,而是直接使用欄位的型別作為欄位的名字。匿名欄位可以是任何資料型別,甚至可以是結構型別。當結構中包含結構型別的匿名欄位時,這些結構欄位會自動成為外層結構的一部分,這就實現了結構之間的內嵌和組合。
這種特性可以將 Go 中的結構設計與面向物件語言中的繼承概念相提並論。Go 語言中的“繼承”並不透過傳統的繼承機制實現,而是通過內嵌和組合來達成的。因此,在 Go 中,組合(composition)比繼承更為重要。
這篇文章將深入介紹 Go 中的匿名欄位的基本概念和用法,並展示如何利用它們來簡化程式碼結構,達到類似繼承的效果。
圖片來源:Gophers
🔍 什麼是匿名欄位?
匿名欄位是指結構中的欄位不使用顯式的欄位名稱,而是直接使用欄位的型別作為名稱。這種欄位稱為匿名欄位,它使得程式碼更加簡潔,並且允許直接通過欄位的資料類型來訪問欄位。
範例:基本型別的匿名欄位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package main
import "fmt"
// 定義包含匿名欄位的結構
type Person struct {
string // 匿名欄位,使用基本型別 string
int // 匿名欄位,使用基本型別 int
}
func main() {
// 創建一個 Person 結構實例
p := Person{"Alice", 30}
// 訪問匿名欄位的欄位
fmt.Println("Name:", p.string) // 輸出: Name: Alice
fmt.Println("Age:", p.int) // 輸出: Age: 30
}📝 在這個範例中,我們定義了
Person
結構,它包含兩個匿名欄位:一個string
類型和一個int
類型。我們在main
函式中創建了一個Person
結構的實例,並使用匿名欄位的類型名稱來訪問欄位。範例:結構型別作為匿名欄位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package main
import "fmt"
// 定義 Address 結構
type Address struct {
City string
State string
}
// 定義 Person 結構,匿名欄位嵌入 Address 結構
type Person struct {
Name string
Age int
Address // 匿名欄位,直接使用 Address 結構
}
func main() {
// 創建一個 Person 結構實例
p := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "New York",
State: "NY",
},
}
// 訪問匿名欄位的欄位
fmt.Println("Name:", p.Name) // 輸出: Name: Alice
fmt.Println("Age:", p.Age) // 輸出: Age: 30
fmt.Println("City:", p.City) // 輸出: City: New York
fmt.Println("State:", p.State) // 輸出: State: NY
}📝 在這個範例中,
Person
結構中嵌入了Address
結構作為匿名欄位。這樣,Person
便能直接訪問Address
結構中的欄位City
和State
,就像它們是Person
結構的欄位一樣。
🛠 匿名欄位的特點與用途
- 簡化程式碼結構: 匿名欄位可以減少多餘的欄位名稱,讓程式碼結構變得更加簡潔。例如,當你想將一個結構嵌入到另一個結構中,並且不需要額外的欄位名稱時,匿名欄位非常有用。
- 模擬繼承: 在 Go 語言中,沒有傳統意義上的繼承機制,但匿名欄位可以實現類似繼承的效果。當一個結構嵌入另一個結構時,嵌入結構的欄位會成為外層結構的一部分,這樣就能夠模擬類似繼承的行為。
- 範例:模擬繼承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package main
import "fmt"
// 定義一個父結構
type Animal struct {
Name string
}
// 定義子結構,嵌入父結構作為匿名欄位
type Dog struct {
Animal // 匿名欄位
Breed string
}
func main() {
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
// 訪問匿名欄位中的欄位
fmt.Println("Name:", d.Name) // 輸出: Name: Buddy
fmt.Println("Breed:", d.Breed) // 輸出: Breed: Golden Retriever
}📝 在這個範例中,
Dog
結構嵌入了Animal
結構作為匿名欄位,這樣Dog
便可以直接訪問Animal
結構的欄位Name
。
- 範例:模擬繼承
⚠️ 注意事項
- 名稱衝突: 當結構中有多個匿名欄位具有相同的名稱時,會產生名稱衝突。Go 語言允許這樣做,但如果在嵌入結構時有欄位名稱重複,則會引發錯誤。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import "fmt"
type Person struct {
Name string
}
type Employee struct {
Person // 匿名欄位
Name string // 與 Person 結構中的 Name 欄位重名
}
func main() {
e := Employee{
Person: Person{Name: "Alice"},
Name: "Bob",
}
fmt.Println("Employee Name:", e.Name) // 輸出: Employee Name: Bob
}📝 在這個範例中,
Person
結構和Employee
結構都有名為Name
的欄位,因此e.Name
會選擇Employee
結構中的Name
欄位,而不是從Person
結構繼承的Name
。
- 範例:
- 匿名欄位作為傳值類型:當結構作為值傳遞時,匿名欄位會被複製,因此對匿名欄位的修改不會影響原始結構。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee struct {
Person // 匿名欄位
Salary int
}
func updateAge(emp Employee, newAge int) {
emp.Age = newAge // 修改匿名欄位 Person 的 Age
fmt.Println("Inside updateAge - Name:", emp.Name, "Age:", emp.Age)
}
func main() {
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Salary: 50000,
}
// 傳遞結構的複製,updateAge 函式內部修改的是副本
updateAge(e, 35)
// 查看 main 中的 e 的值
fmt.Println("Outside updateAge - Name:", e.Name, "Age:", e.Age)
}📝 在這個範例中,
Age
被修改的只是updateAge
函式內的副本,而不是原始的結構。
- 範例:
- 匿名欄位作為指標類型:當結構作為指標傳遞時,對匿名欄位的修改會直接影響原始結構。這是因為指標傳遞的是記憶體位置而非複製。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee struct {
*Person // 匿名指標欄位
Salary int
}
func updateAge(emp *Employee, newAge int) {
emp.Age = newAge // 修改匿名欄位 Person 的 Age
fmt.Println("Inside updateAge - Name:", emp.Name, "Age:", emp.Age)
}
func main() {
p := &Person{Name: "Alice", Age: 30}
e := &Employee{
Person: p, // 將指標賦給匿名欄位
Salary: 50000,
}
// 傳遞結構指標,更新指標欄位的內容
updateAge(e, 35)
// 查看 main 中的 e 的值
fmt.Println("Outside updateAge - Name:", e.Name, "Age:", e.Age)
}📝 在這個範例中,傳遞的是結構的指標,所以對
Age
的修改會影響原始結構中的欄位。
- 範例:
🎯 總結
- 匿名欄位讓你可以在 Go 中更簡潔地定義結構,並且直接訪問嵌入結構的欄位。
- 它有助於實現類似繼承的行為,使得程式碼更具可重用性。
- 在使用匿名欄位時要小心名稱衝突和傳值問題。
最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。
註:以上參考了
Go