Go中接口组合+嵌入实现逻辑复用:将可变行为抽为接口、固定流程放入结构体并嵌入;嵌入不自动实现接口,需显式定义方法;IDProvider等通用结构体嵌入可共享实现;动态策略应使用接口字段依赖而非嵌入。

Go 里没有继承,但接口组合 + 嵌入能真正复用逻辑,而不是只复用方法签名——关键在于把“可变行为”抽成接口,把“固定流程”放进结构体,再通过嵌入获得能力。
为什么直接嵌入结构体无法复用接口实现?
常见误解是:只要 type User struct { Base },就能让 User 自动满足 Describer 接口。错在:如果 Base 本身没实现该接口,嵌入不会自动“转发”调用到 User;User 必须自己实现接口方法,否则编译失败。
- 嵌入只提升字段和方法可见性,不自动实现接口
-
User满足Describer的唯一方式是显式定义Describe()方法(哪怕只是调用u.Base.Describe()) - 若想让多个类型共用同一套
Describe()实现,必须把它写在某个结构体里,再让各类型嵌入它
如何用嵌入 + 接口让多个类型共享同一套逻辑?
核心是把通用行为封装进一个结构体,再通过嵌入“带进去”。比如日志前缀、ID 生成、JSON 序列化规则等,都适合这种模式。
- 定义一个
Identifiable接口:type Identifiable interface { ID() string } - 写一个
IDProvider结构体,含id string字段和ID()方法 - 让
User、Product、Order都嵌入IDProvider,它们就自动满足Identifiable - 所有类型共享同一份
ID()实现,改一处,全生效
示例:type User struct { IDProvider Name string },无需再写 User.ID()。
立即学习“go语言免费学习笔记(深入)”;
什么时候该用接口字段依赖,而不是嵌入?
当逻辑不是“每个类型都有一份”,而是“每个类型按需委托给不同实现”时,嵌入就不合适了——比如验证、处理、保存这些步骤,不同业务需要不同策略。
- 用嵌入适合“静态复用”(字段+方法固定,所有子类型都一样)
- 用接口字段适合“动态委托”(如
validator Validator、processor Processor) - 错误做法:
Base里存self ExtenderInterface并在构造时赋值&e,这会导致循环引用和初始化不确定性 - 正确做法:通过构造函数注入,例如
NewUser(v Validator, p Processor),Base只持接口,不碰具体类型
容易被忽略的初始化顺序问题
嵌入结构体的字段初始化发生在外层结构体之前,但方法调用时机取决于你何时调用——如果在 User 字段还没赋值时就调用了嵌入的 IDProvider.ID(),而该方法又依赖 User.Name 这类字段,就会 panic。
- 嵌入结构体不能安全访问外层结构体的字段,除非你显式传参或通过闭包捕获
- 若逻辑强依赖外层状态,不要把它塞进嵌入结构体,改用接口回调或函数字段
- 测试时最容易暴露这个问题:零值
User{}调用ID()可能返回空字符串或 panic,得提前 guard


















