← 结构体 | Struct Tag →

嵌套与嵌入 - 结构体组合

Go 通过结构体嵌套和嵌入实现代码复用,这是 Go 组合优于继承理念的体现。掌握匿名嵌入和字段提升是编写优雅 Go 代码的关键。

嵌套结构体

📝 命名嵌套

package main

import "fmt"

type Address struct {
    City    string
    District string
    Street  string
}

type Person struct {
    Name    string
    Age     int
    Address Address // 命名嵌套
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:     "Beijing",
            District: "Chaoyang",
            Street:   "Main St",
        },
    }
    
    // 访问嵌套字段
    fmt.Printf("%s lives in %s, %s\n", 
        p.Name, p.Address.City, p.Address.District)
}

匿名嵌入

📝 字段提升

package main

import "fmt"

type Address struct {
    City   string
    Street string
}

type Person struct {
    Name string
    Age  int
    Address // 匿名嵌入,字段提升
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:   "Beijing",
            Street: "Main St",
        },
    }
    
    // 字段提升:可以直接访问
    fmt.Println(p.City)   // 等价于 p.Address.City
    fmt.Println(p.Street)  // 等价于 p.Address.Street
    
    // 也可以显式访问
    fmt.Println(p.Address.City)
}

💡 嵌入要点

  • 字段提升: 嵌入类型的字段提升到外层
  • 访问方式: 可以直接访问或通过嵌入类型访问
  • 方法提升: 嵌入类型的方法也会提升
  • 名称冲突: 同名字段会隐藏嵌入字段

嵌入类型的方法

📝 方法提升

package main

import "fmt"

type Logger struct {
    prefix string
}

func (l Logger) Info(msg string) {
    fmt.Printf("[%s] INFO: %s\n", l.prefix, msg)
}

func (l Logger) Error(msg string) {
    fmt.Printf("[%s] ERROR: %s\n", l.prefix, msg)
}

type Service struct {
    Name   string
    Logger // 嵌入 Logger
}

func main() {
    svc := Service{
        Name:   "UserService",
        Logger: Logger{prefix: "SVC"},
    }
    
    // 方法提升:可以直接调用
    svc.Info("Service started")
    svc.Error("Something went wrong")
    
    // 也可以显式调用
    svc.Logger.Info("Explicit call")
}

方法重写

📝 覆盖嵌入方法

package main

import "fmt"

type Base struct{}

func (b Base) Greet() {
    fmt.Println("Hello from Base")
}

type Derived struct {
    Base // 嵌入 Base
}

// 重写 Greet 方法
func (d Derived) Greet() {
    fmt.Println("Hello from Derived")
}

func main() {
    d := Derived{}
    
    // 调用重写的方法
    d.Greet() // Hello from Derived
    
    // 调用嵌入类型的方法
    d.Base.Greet() // Hello from Base
}

多重嵌入

📝 嵌入多个类型

package main

import "fmt"

type A struct {
    AField string
}

type B struct {
    BField int
}

type C struct {
    A // 嵌入 A
    B // 嵌入 B
    CField string
}

func main() {
    c := C{
        A:      A{AField: "a-value"},
        B:      B{BField: 42},
        CField: "c-value",
    }
    
    // 访问提升的字段
    fmt.Println(c.AField)   // a-value
    fmt.Println(c.BField)   // 42
    fmt.Println(c.CField)   // c-value
}

⚠️ 名称冲突

// 如果多个嵌入类型有同名字段
type A struct { Name string }
type B struct { Name string }

type C struct {
    A
    B
}

// c.Name 会编译错误!
// 必须显式指定:c.A.Name 或 c.B.Name

最佳实践

✅ 嵌入使用建议

  • 组合优于继承: 使用嵌入实现代码复用
  • 语义清晰: 嵌入应该有"is-a"关系
  • 避免深度嵌套: 超过 2 层考虑重构
  • 注意冲突: 避免同名字段冲突

📖 延伸阅读