Go中适配器模式通过组合和接口隐式实现:定义新类型嵌入旧类型,实现目标接口方法并转发调用;函数适配器适用于无状态简单转换;需注意空指针、接收者类型匹配及循环依赖问题。
Go 没有类继承,所以传统 OOP 中的「适配器继承已有类并实现新接口」行不通。Go 的适配器本质是:写一个新类型,内部持有旧类型实例,再通过方法转发(wrapping)把旧行为“翻译”成新接口要求的签名。关键在于——interface 是隐式满足的,只要新类型实现了目标接口所有方法,它就自动是该接口的实例。
最常用、最清晰的方式:定义一个适配器结构体,字段嵌入被适配对象,然后为该结构体实现目标接口的方法,在方法体内调用嵌入字段的对应方法(必要时做参数/返回值转换)。
type LegacyLogger struct{}
func (l *LegacyLogger) LogMessage(
msg string) {
fmt.Println("[LEGACY] " + msg)
}
type Logger interface {
Print(msg string)
}
type LoggerAdapter struct {
*LegacyLogger // 嵌入,复用原有方法
}
func (a *LoggerAdapter) Print(msg string) {
a.LogMessage("ADAPTED: " + msg) // 转换调用逻辑
}
使用时:var l Logger = &LoggerAdapter{&LegacyLogger{}} 即可,l.Print("hello") 会走适配逻辑。
*LegacyLogger 后,LoggerAdapter 自动获得 LogMessage 方法,但不暴露给 Logger 接口使用者LegacyLogger),否则其方法会泄露到外部,破坏封装当目标接口只有一个方法,且适配逻辑简单(比如只改个参数名或格式),用函数类型包装更简洁。Go 的函数是一等公民,可以直接实现接口(只要它有方法集)。
type Writer interface {
Write([]byte) (int, error)
}
func NewWriterAdapter(writeFunc func(string) error) Writer {
return &writerFuncAdapter{writeFunc}
}
type writerFuncAdapter struct {
write func(string) error
}
func (w *writerFuncAdapter) Write(p []byte) (int, error) {
err := w.write(string(p))
if err != nil {
return 0, err
}
return len(p), nil
}
这种写法省去结构体定义,适合胶水层快速对接第三方回调函数或旧日志库的 func(string) 类型。
Writer 实例是匿名结构体指针,调用方无法对其做类型断言回原函数,安全性更高适配器代码看似简单,但几个细节错一点就 panic 或静默失效:
nil 嵌入字段未判空:如果 LoggerAdapter 字段是 *LegacyLogger,但初始化传了 nil,调用 a.LogMessage 就 panic —— 必须在适配方法里加 if a.LegacyLogger == nil { ... }
LegacyLogger.LogMessage 是值接收者,嵌入 LegacyLogger(非指针)才可用;但若它是指针接收者,就必须嵌入 *LegacyLogger,否则编译报错 “cannot call pointer method on …”adapters),若它 import 了业务接口包,而业务包又 import 了适配器,就会循环依赖 —— 解法是把目标接口定义在公共基础包,或让适配器只依赖最小契约(如只 import io.Writer 而非具体业务接口)适配器真正的难点不在语法,而在厘清谁是“旧”,谁是“新”,以及哪一层该承担转换责任——多一层包装就多一层维护成本,别为了模式而模式。