第二十九回:Go 组合

移花接木神功(组合 Composition)
唐伯虎
唐伯虎: 华夫人,听说西洋武学讲究“继承”(Inheritance),儿子自动继承老爸的所有武功。但我们 Go 派武学讲究的是“移花接木”(组合 Composition)。我们不搞复杂的族谱,我们直接把别人的内力“缝”在自己身上!

💡 核心概念: Go 语言没有 extends 关键字,不支持传统的类继承。Go 使用结构体嵌入(Struct Embedding)来实现代码复用。这是一种“Has-a”(有一个)关系,而不是“Is-a”(是一个)关系,但用起来很像继承。

🧩 第一式:内力嫁接 (结构体嵌入)

我们定义一个基础结构体 Person,然后把它“嵌入”到 Maid(侍女)中。注意,我们只写类型名 Person,不写字段名,这叫匿名字段

package main
import "fmt"

// 基础结构体:人
type Person struct {
    Name string
    Age  int
}

// Person 的方法
func (p *Person) SayHello() {
    fmt.Printf("大家好,我是 %s,今年 %d 岁。\n", p.Name, p.Age)
}

// 侍女结构体
type Maid struct {
    Person  // 👈 核心:直接嵌入 Person 结构体
    Skill   string
}

func main() {
    // 初始化的时候,要指定内部结构体
    qiuxiang := Maid{
        Person: Person{
            Name: "秋香",
            Age:  18,
        },
        Skill: "吟诗作对",
    }

    // ✨ 神奇之处:直接访问 Person 的字段
    fmt.Println(qiuxiang.Name) // 等同于 qiuxiang.Person.Name

    // ✨ 神奇之处:直接调用 Person 的方法 (方法提升)
    qiuxiang.SayHello() 
}
秋香
秋香: 哇,我明明没有定义 SayHello 方法,但我却能直接用!这就好像我天生就会一样,不用显式地写 qiuxiang.Person.SayHello() 这么麻烦。

⚔️ 第二式:青出于蓝 (方法重写/覆盖)

如果 Maid 自己也定义了一个 SayHello 方法,会发生什么呢?Go 会优先使用外层的(自己的)方法。这叫方法屏蔽(Shadowing)

// Maid 自己定义了 SayHello
func (m *Maid) SayHello() {
    fmt.Printf("小女子 %s 给公子请安了~ (技能:%s)\n", m.Name, m.Skill)
}

func main() {
    qiuxiang := Maid{
        Person: Person{Name: "秋香"},
        Skill:  "点秋香",
    }

    // 调用的是 Maid 自己的 SayHello
    qiuxiang.SayHello() 
    
    // 如果非要调用老爸的,只能显式调用
    qiuxiang.Person.SayHello()
}
华夫人
华夫人: 哼,算你识相!这就叫“县官不如现管”,自己的招式优先级最高!

🌪️ 第三式:集百家之长 (多重组合)

Go 的组合比继承更灵活,因为你可以嵌入多个结构体,就像学会多种武功!

👹 走火入魔警告(命名冲突):

如果 PersonKungFu 都有一个叫 Name 的字段,那 monk.Name 到底是谁的?

华夫人: 笨蛋!这时候编译器会晕倒(报错:ambiguous selector)。你必须显式指定 monk.Person.Namemonk.KungFu.Name

type KungFu struct {
    Style string
}

func (k *KungFu) Attack() {
    fmt.Println("使出了", k.Style)
}

// 护院武僧:既是人,又会功夫
type Monk struct {
    Person
    KungFu
}

func main() {
    monk := Monk{
        Person: Person{Name: "夺命书生"},
        KungFu: KungFu{Style: "面目全非脚"},
    }
    
    monk.SayHello() // 来自 Person
    monk.Attack()   // 来自 KungFu
}

🎯 练功房(招式判断)

当结构体嵌入和方法重写同时存在时,请判断下面的代码会输出什么。

package main
import "fmt"

type Master struct {}

func (m *Master) Teach() {
    fmt.Println("教你画画")
}

type Student struct {
    Master
}

// Student 重写了 Teach
func (s *Student) Teach() {
    fmt.Println("教你捉弄人")
}

func main() {
    s := Student{}
    s.Teach()
}

任务: 最终会输出什么?

答案: "教你捉弄人"

解析: 因为 Student 定义了自己的 Teach 方法,所以它“屏蔽”了内部 MasterTeach 方法。这就是 Go 语言中的“重写”机制。