Like Share Discussion Bookmark Smile

J.J. Huang   2025-03-18   Getting Started Golang 05.結構   瀏覽次數:次   DMCA.com Protection Status

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
    18
    package 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
    34
    package 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 結構中的欄位 CityState,就像它們是 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
      25
      package 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
      20
      package 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
      31
      package 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
      32
      package 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