Go 语言通过结构体匿名嵌入和接口组合实现复用与解耦,而非继承;嵌入需无字段名、注意指针接收者调用、避免同名冲突;接口仅含方法,应小而专;依赖注入优于循环引用;嵌入提升字段方法,命名嵌套保持层级。
Go 语言里没有继承,但你完全可以用结构体嵌入 + 接口组合写出可复用、易测试、不耦合的代码。关键不是“怎么模拟父类”,而是“如何把共性抽出来、把变化点交出去”。
匿名嵌入是让外层结构体“自动获得”内层字段和方法的核心机制,但容易在初始化、指针接收者、字段冲突上出错。
Base,不能写成 b Base(那是命名嵌套,不触发提升)Base 的方法用的是指针接收者(如 func (b *Base) GetID() int),那只有外层结构体取地址后才能调用——e.GetID() 会报错,得写成 (&e).GetID()
A 和 B 都有 ID 字段,type C struct { A; B } 就无法编译json:"id")会被保留,但要注意:若外层也定义同名字段,标签以**外层为准**Go 的 interface 只能声明方法签名,不能包含字段或具体实现。这是设计使然,不是限制——它强制你把“数据”和“行为”分开管理,避免状态与逻辑混杂。
type Foo interface { Name string; Do() } → 编译失败,string 不是方法Base),用接口约束行为(如 Describer),两者通过组合协作Validator、Logger、Serializer,而不是一个大而全的 BaseInterface —— 否则实现方被迫实现一堆用不到的方法常见反模式是让 Base 持有子类型接口,并在子类型里把自己赋给 Base 字段(如 e.Base.B = &e)。这看似实现了“父类调子类”,实则埋下 panic 隐患。
立即学习“go语言免费学习笔记(深入)”;
e.Base.B 是 nil,调用 e.Base.SomeMethod() 直接 panicBase 还没构造完,e 就已开始调用其方法Base 行为,因为它的逻辑强依赖子类型实例NewUser(logger Logger, validator Validator),Base 只持接口字段,不参与生命周期管理两者都算“组合”,但语义和使用方式完全不同,选错会导致 API 难用、序列化异常、升级脆弱。
Base)→ 字段和方法被“提升”,User.Name、User.GetID() 可直接访问;JSON 序列化时字段平铺(除非显式忽略)Addr Address)→ 必须写 u.Addr.City;JSON 默认生成嵌套对象 {"addr": {"city": "Beijing"}}
Home Address, Work Address)真正难的不是语法,而是判断哪些该放进嵌入结构体、哪些该抽成接口、哪些该由调用方传入。每次加一个新字段或方法前,先问一句:这个东西会不会被其他类型复用?它是否应该独立于当前业务逻辑存在?答案决定你该写 Base 还是 Describer,还是干脆就别抽象。