如何在Golang中实现适配器模式_Golang适配器模式接口转换方法

Go中适配器模式通过组合和接口隐式实现:定义新类型嵌入旧类型,实现目标接口方法并转发调用;函数适配器适用于无状态简单转换;需注意空指针、接收者类型匹配及循环依赖问题。

适配器模式在 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 interf

ace { 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 { ... }
  • 值接收者 vs 指针接收者:若 LegacyLogger.LogMessage 是值接收者,嵌入 LegacyLogger(非指针)才可用;但若它是指针接收者,就必须嵌入 *LegacyLogger,否则编译报错 “cannot call pointer method on …”
  • 循环 import:适配器常位于独立包(如 adapters),若它 import 了业务接口包,而业务包又 import 了适配器,就会循环依赖 —— 解法是把目标接口定义在公共基础包,或让适配器只依赖最小契约(如只 import io.Writer 而非具体业务接口)

适配器真正的难点不在语法,而在厘清谁是“旧”,谁是“新”,以及哪一层该承担转换责任——多一层包装就多一层维护成本,别为了模式而模式。