Go通过结构体嵌入实现组合而非继承,提升导出字段和方法,支持接口组合与行为复用,强调“有一个”关系及职责分离。

Go语言没有传统面向对象的继承机制,但通过结构体嵌入(embedding)可以自然、简洁地实现组合模式——即“有一个”(has-a)而非“是一个”(is-a)的关系。这种方式更强调行为复用与职责分离,是Go推荐的代码组织方式。
结构体嵌入的本质是字段提升
当一个结构体被匿名嵌入另一个结构体时,Go会将被嵌入类型的所有导出字段和方法“提升”(promoted)到外层结构体中,就像它们直接定义在外层一样。这不是继承,不涉及类型层级或虚函数表,只是编译器自动做的语法糖。
- 嵌入字段必须是类型名,不能带字段名(即匿名):如 Logger 而非 log Logger
- 只有导出(大写开头)的字段和方法才会被提升
- 若外层结构体已存在同名字段或方法,会屏蔽嵌入类型的对应成员
用嵌入实现接口组合与行为复用
组合模式的核心在于让类型获得新能力,而不改变其原有语义。常见做法是嵌入一个提供某类行为的结构体,并让外层类型自动满足相关接口。
例如,为 HTTP 处理器添加日志和重试能力:
立即学习“go语言免费学习笔记(深入)”;
// 日志行为封装
type LoggingHandler struct{}
func (l LoggingHandler) Log(msg string) { fmt.Println("[LOG]", msg) }
// 重试行为封装
type Retryable struct{}
func (r Retryable) DoWithRetry(f func() error, max int) error {
for i := 0; i < max; i++ {
if err := f(); err == nil {
return nil
}
time.Sleep(time.Second)
}
return errors.New("failed after retries")
}
// 组合:HTTP处理器同时拥有日志和重试能力
type APIHandler struct {
LoggingHandler
Retryable
handler http.HandlerFunc
}
func (h APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Log("handling request: " + r.URL.Path)
h.Retryable.DoWithRetry(func() error {
h.handler(w, r)
return nil
}, 3)
}
此时 APIHandler 自动具备 Log 和 DoWithRetry 方法,无需重复编写,也未破坏其作为 http.Handler 的身份。
嵌入接口:组合契约而非实现
除了嵌入具体结构体,还可嵌入接口类型。这表示“我依赖某个行为契约”,由运行时注入具体实现,增强灵活性与可测试性。
- 嵌入接口后,外层结构体自动满足该接口(只要它实现了接口所有方法)
- 更常见的是:外层结构体持有一个接口字段(非嵌入),再通过嵌入“委托结构体”间接实现
- 例如:嵌入 io.Writer 接口,意味着该类型承诺可被写入;实际写入逻辑由外部传入
这种写法把“能力声明”和“能力实现”解耦,是组合优于继承的关键体现。
注意命名冲突与方法集差异
嵌入虽方便,但需警惕两个常见陷阱:
- 方法集不同:嵌入类型的方法接收者若为指针(*T),则只有 *Outer 拥有该方法,Outer 值类型实例不拥有——这点常导致接口实现失败
- 同名方法屏蔽:若外层定义了与嵌入类型同名的方法,会完全覆盖嵌入版本;若想调用原方法,需显式通过嵌入字段名访问,如 h.LoggingHandler.Log(...)
因此建议:嵌入前检查方法签名,优先使用指针接收者保持一致性;对关键行为,考虑显式字段+委托,而非隐式嵌入。


















