go 语言不支持继承,嵌入结构体仅为字段和方法提升,非类型继承;接口定义行为契约,组合强调显式依赖与职责分离。

Go 语言里压根没有继承这回事,extends、super、override 全都不支持——不是还没实现,是设计上就拒绝。你写不出,编译器直接报错。
嵌入结构体 ≠ 继承,只是字段和方法的自动提升
当你写 type Dog struct { Animal },Dog 并不是 Animal 的子类,它只是把 Animal 的导出字段和方法“拉进自己的命名空间”。调用 d.Speak() 看似是 Dog 在说话,实际等价于 d.Animal.Speak(),接收者仍是 *Animal 或 Animal,跟 Dog 本身无关。
- 如果
Animal的方法全是值接收者(func (a Animal) Speak()),嵌入Animal值类型没问题;但只要有一个指针接收者(func (a *Animal) Start()),就必须嵌入*Animal,否则d.Start()编译失败:"cannot call pointer method on d.Animal" - 嵌入
*Animal后,若初始化时传了nil,比如Dog{Animal: nil},一调d.Start()就 panic:nil pointer dereference - 两个嵌入类型若有同名方法(如都含
Log()),d.Log()会报错:ambiguous selector d.Log,必须显式写成d.Animal.Log()或d.Logger.Log()
接口才是 Go 中真正的“行为契约”,不是嵌入决定的
能不能当某个接口的实现者,不看你嵌了谁,只看你有没有实现它的全部方法签名。嵌入 Animal 不会让 Dog 自动满足 Speaker 接口——哪怕 Animal 已经实现了 Speak(),你也得在 Dog 上再写一遍,或者靠嵌入指针+方法提升间接满足(前提是方法签名完全一致)。
- 定义
type Speaker interface { Speak() }后,Dog和Cat可各自独立实现,互不影响;改Cat.Speak()不会影响Dog,也不会破坏任何已有调用 - 接口嵌入(如
type ReadWriter interface { io.Reader; io.Writer })是合法且推荐的,它只是方法集的并集展开,不是类型继承;被嵌入接口的方法会“扁平化”进外层接口 - 别指望接口能带字段或状态——接口只管“能做什么”,不管“有什么”。共享数据必须靠结构体嵌入,不能塞进接口里
组合的关键在于依赖显式化,不是少写几行代码
真正体现组合思想的,是把“能力”抽象成接口,把“实现”作为字段注入,而不是嵌入另一个结构体。比如日志功能,应该写 logger Logger,而不是 LoggerImpl。
-
type Server struct { db *sql.DB; cache Cache; logger Logger }—— 一眼看出它依赖什么、怎么初始化、哪里可替换 - 测试时直接传
&mockDB{}或&inmemoryCache{},不用 mock 整个父类或绕过构造逻辑 - 运行时换组件零成本:把
cache从*redis.Client换成*ristretto.Cache,只要都实现了Cache接口,上层代码完全不动 - 嵌入结构体容易导致隐式生命周期耦合——一旦嵌了
*Config,你就得负责它不为nil、不被并发修改、不提前释放
最容易被忽略的点是:组合不是语法技巧,它是职责边界的硬性约束。嵌入一个结构体,等于签了一份合约——你承诺管理它的生命周期、理解它的线程安全模型、接受它所有方法的语义。接口则相反:你只承诺行为,不承诺实现细节。这两层约束一起,才让 Go 的代码在规模起来之后依然可控。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











