适配器需通过组合+接口转换实现委托调用,而非继承或embed:定义目标接口,结构体持非导出旧对象字段并显式实现新接口,仅做无歧义参数/返回值映射,不暴露旧字段、不引入状态、不掺杂业务逻辑。

Go 里怎么写一个不破坏原有代码的适配器
适配器不是加一层壳就完事,核心是让旧 DoWork() 这类函数/接口能被新系统直接调用,同时不改它一行源码。关键在「委托」而非「继承」——Go 没继承,靠组合+接口转换。
常见错误是把适配器写成新功能封装体,结果旧逻辑被绕过,或者强塞泛型导致调用方必须改类型。实际只需两步:定义目标接口、实现该接口并内部调用旧对象。
- 旧系统有个
LegacyProcessor类型,只有Process(data string) error方法 - 新系统期望接收
Processor接口,含Execute(input []byte) (result int, err error) - 适配器就该长这样:
type LegacyAdapter struct { legacy *LegacyProcessor } func (a *LegacyAdapter) Execute(input []byte) (int, error) { return 0, a.legacy.Process(string(input)) // 委托调用,不碰 legacy 源码 }
接口不匹配时怎么处理参数和返回值转换
旧接口用 string,新接口要 []byte;旧返回 error,新要带 int 结果——这类转换最容易漏掉边界情况。
别在适配器里做业务逻辑判断,只做无歧义的映射。比如 string → []byte 直接用 []byte(s) 就行,但若旧方法对空字符串有特殊处理,适配器就得同步复现,否则调用方会遇到意料外行为。
立即学习“go语言免费学习笔记(深入)”;
- 输入转换:优先用 Go 标准方式(
[]byte、string()、strconv.Atoi),避免自定义解析逻辑 - 返回值补位:新接口多出的
int如果旧逻辑不提供,就固定返回0或1,别试图“猜”语义 - 错误处理:旧方法返回
nil表示成功,新接口要求非零result才算成功?那就得在适配器里统一约定,比如result = 1对应成功,result = 0对应失败
为什么不用 embed 把旧类型直接塞进新结构体
看起来省事:type Adapter struct { *LegacyProcessor },但这是陷阱。embed 只转发方法签名,不帮你做参数/返回值转换,一旦接口方法名相同但签名不同(比如参数类型不一样),编译直接报错:cannot embed LegacyProcessor: conflicting types。
更麻烦的是,embed 后你没法拦截调用过程——想加日志、重试、类型转换?做不到。适配器的价值恰恰在“中间可控”,而不是透明透传。
- embed 适合「完全兼容」场景,比如旧接口就是新接口的子集,且参数返回值一字不差
- 只要存在任何类型差异、语义差异、错误处理差异,就必须显式实现目标接口,不能依赖 embed
- 测试时容易误判:用 embed 的适配器跑通了,不代表真实集成没问题,因为转换逻辑根本没走
适配器要不要导出内部旧对象字段
不要。导出 Legacy 字段(比如 Legacy *LegacyProcessor)等于把耦合暴露出去,调用方可能绕过适配逻辑直接调旧方法,后续升级或替换旧实现时就会崩。
适配器应是黑盒:输入符合新接口,输出也只通过新接口契约交付。哪怕测试需要访问底层,也该用非导出字段 + 测试友好的构造函数(比如接受 LegacyProcessor 的 option 参数),而不是开放字段。
- 构造函数可设计为
NewLegacyAdapter(legacy *LegacyProcessor),内部存为 unexported 字段legacy *LegacyProcessor - 如果真要调试,加个
DebugLegacy() *LegacyProcessor方法,仅限测试文件里用,不导出字段本身 - 导出字段还会让 Go vet 提示 “field is unused” —— 因为正常调用路径根本不需要它
最常被忽略的一点:适配器自身不该有状态。如果旧对象是无状态的,适配器也该是;但如果旧对象依赖全局配置或外部连接,适配器就得明确承担初始化责任——这个初始化时机,往往比写几行转换代码更难对齐。










