Go 继承
Go 没有继承:用嵌入做组合、用接口做多态、用委托做复用。
先说结论
Go has no classes, no inheritance, no virtual functions, no extends. It offers two substitutes: embedding (reuse fields and methods) and interfaces (polymorphism). People arriving from Java or C++ look for inheritance first, then discover composition.
嵌入
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " 发出声音"
}
func (a Animal) Move() string {
return a.Name + " 移动"
}
type Dog struct {
Animal // 嵌入
Breed string
}
d := Dog{Animal: Animal{Name: "旺财"}, Breed: "柴犬"}
fmt.Println(d.Name) // 提升的字段
fmt.Println(d.Speak()) // 提升的方法
fmt.Println(d.Move())旺财
旺财 发出声音
旺财 移动覆盖(不是重写)
// Dog 自己定义 Speak,遮蔽 Animal 的
func (d Dog) Speak() string {
return d.Name + " 汪汪叫"
}
d := Dog{Animal: Animal{Name: "旺财"}}
fmt.Println(d.Speak()) // Dog 的版本
fmt.Println(d.Animal.Speak()) // 显式调用被遮蔽的旺财 汪汪叫
旺财 发出声音Dog.Speak does not affect calls to Speak inside Animal.Move — Go has no vtable. To dispatch, pass an interface into Move.用接口实现真正的多态
type Speaker interface {
Speak() string
}
func announce(s Speaker) {
fmt.Println("广播:", s.Speak())
}
type Cat struct{ Name string }
func (c Cat) Speak() string { return c.Name + " 喵喵叫" }
announce(Dog{Animal{Name: "旺财"}, "柴犬"})
announce(Cat{Name: "咪咪"})广播: 旺财 汪汪叫
广播: 咪咪 喵喵叫Interfaces are Go's polymorphism. announce depends on exactly one method, so any type implementing it works — far more flexible than a class hierarchy, and no upfront design needed.
嵌入接口
type Reader interface{ Read(p []byte) (int, error) }
type Closer interface{ Close() error }
// 接口可以嵌入接口
type ReadCloser interface {
Reader
Closer
}
// 结构体也可以嵌入接口,用来「部分实现」或做 mock
type MockReadCloser struct {
Reader // 嵌入接口,未实现的方法会 panic
}
func (m MockReadCloser) Close() error { return nil }Embedding an interface in a struct is a mocking trick: implement only the methods under test and leave the rest to a nil interface — calling them panics, which reminds you they are unstubbed.
组合优于继承的实例
// 继承的写法会得到「正方形是不是矩形」这类问题
// Go 的写法:各自独立,共享能力用接口
type Logger interface{ Log(msg string) }
type Service struct {
Logger // 注入日志能力
DB *sql.DB
}
func (s *Service) Create(name string) error {
s.Log("创建 " + name)
return nil
}| Need | Go way |
|---|---|
| 复用字段 | 嵌入结构体 |
| 复用方法 | 嵌入 + 委托 |
| 多态 | 接口 |
| 扩展第三方类型 | 包装(组合)+ 方法 |
| 模板方法模式 | 把函数当参数传 |
type MyTime struct{ time.Time } and add your own methods. That is Go's standard "extension".先说结论
Go 没有类、没有继承、没有虚函数、没有 extends。它提供两个替代品:嵌入(复用字段与方法)和接口(多态)。很多从 Java/C++ 转过来的人第一件事就是找继承,然后发现应该用组合。
嵌入
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " 发出声音"
}
func (a Animal) Move() string {
return a.Name + " 移动"
}
type Dog struct {
Animal // 嵌入
Breed string
}
d := Dog{Animal: Animal{Name: "旺财"}, Breed: "柴犬"}
fmt.Println(d.Name) // 提升的字段
fmt.Println(d.Speak()) // 提升的方法
fmt.Println(d.Move())旺财
旺财 发出声音
旺财 移动覆盖(不是重写)
// Dog 自己定义 Speak,遮蔽 Animal 的
func (d Dog) Speak() string {
return d.Name + " 汪汪叫"
}
d := Dog{Animal: Animal{Name: "旺财"}}
fmt.Println(d.Speak()) // Dog 的版本
fmt.Println(d.Animal.Speak()) // 显式调用被遮蔽的旺财 汪汪叫
旺财 发出声音Dog 的 Speak 不会影响 Animal 的 Move 内部对 Speak 的调用——因为 Go 没有虚函数表。想在 Move 里调用子类的 Speak,只能把接口传进去。用接口实现真正的多态
type Speaker interface {
Speak() string
}
func announce(s Speaker) {
fmt.Println("广播:", s.Speak())
}
type Cat struct{ Name string }
func (c Cat) Speak() string { return c.Name + " 喵喵叫" }
announce(Dog{Animal{Name: "旺财"}, "柴犬"})
announce(Cat{Name: "咪咪"})广播: 旺财 汪汪叫
广播: 咪咪 喵喵叫接口才是 Go 的多态机制。announce 只依赖 Speak 这一个方法,任何实现了它的类型都能传进来——这比继承体系灵活得多,也不用提前设计类层次。
嵌入接口
type Reader interface{ Read(p []byte) (int, error) }
type Closer interface{ Close() error }
// 接口可以嵌入接口
type ReadCloser interface {
Reader
Closer
}
// 结构体也可以嵌入接口,用来「部分实现」或做 mock
type MockReadCloser struct {
Reader // 嵌入接口,未实现的方法会 panic
}
func (m MockReadCloser) Close() error { return nil }结构体嵌入接口是写 mock 的技巧:只实现你要测的方法,其他方法留给嵌入的 nil 接口——调用到就会 panic,正好提醒你「这个方法没 mock」。
组合优于继承的实例
// 继承的写法会得到「正方形是不是矩形」这类问题
// Go 的写法:各自独立,共享能力用接口
type Logger interface{ Log(msg string) }
type Service struct {
Logger // 注入日志能力
DB *sql.DB
}
func (s *Service) Create(name string) error {
s.Log("创建 " + name)
return nil
}| 想要 | Go 的做法 |
|---|---|
| 复用字段 | 嵌入结构体 |
| 复用方法 | 嵌入 + 委托 |
| 多态 | 接口 |
| 扩展第三方类型 | 包装(组合)+ 方法 |
| 模板方法模式 | 把函数当参数传 |
type MyTime struct{ time.Time },然后加自己的方法。这就是 Go 里「扩展」的标准做法。