go中适配器模式核心是不改老代码,用新结构体或函数将旧接口“翻译”为新接口;结构体适配器推荐显式字段以避免意外暴露方法,函数适配器适用于单方法接口且更轻量;适配器仅做协议转换,不维护状态、不重试、不熔断,专注统一入口与错误归一化。

Go 里用适配器模式,核心就一条:不改老代码,靠新结构体或函数把接口“翻译”过来。它不是为了炫技,而是当你被第三方库、旧日志、不同短信 SDK 或文件读取方式卡住时,最务实的破局手段。
怎么写一个结构体适配器(嵌入 vs 显式字段)
多数场景下,你拿到一个已有类型(比如 LegacyLogger),它有 LogError(msg string),但系统要求你提供 Logger 接口(含 Info/Error/Debug)。这时你要决定——是嵌入还是显式持有?
- 用嵌入(
*LegacyLogger)适合它已有大量可复用方法,且你不介意这些方法“意外暴露”给调用方;但一旦嵌入,LegacyLogger的Close()或SetLevel()也会变成适配器的方法,可能破坏接口契约 - 用显式字段(
logger *LegacyLogger)更安全,所有转发都手动控制,不会泄露无关行为;推荐在生产代码中默认选它 - 别让适配器自己存状态(比如缓存、计数器),它只做协议转换;加时间戳可以,但做日志级别过滤(比如把
Debug直接丢弃)就超职责了
示例:LegacyLoggerAdapter 显式持有并转发:
type LegacyLoggerAdapter struct {
logger *LegacyLogger
}
func (a *LegacyLoggerAdapter) Error(msg string) {
a.logger.LogError(msg) // 直接调用,不改逻辑
}
func (a *LegacyLoggerAdapter) Info(msg string) {
a.logger.LogError("[INFO] " + msg) // 仅加前缀,不引入新行为
}
什么时候该用函数适配器(而不是结构体)
当被适配的是一个独立函数(比如 sendEmail(string) error),而目标接口只有一个方法(如 Writer.Write([]byte) (int, error)),函数适配器比定义结构体更轻、更干净。
环保节能智能空气净化器类网站模板(带手机版)安装即用,自带人人站CMS内核,支持移动端,前端banner轮播图文本均已进行可视化配置,伪静态页面生成,支持内容模型,支持多种URL模式及模型。模板特点:1、安装即用,自带人人站CMS内核及企业站展示功能(产品,新闻,案例展示等),并可根据需要增加表单 搜索等功能(自带模板) 2、支持响应式 3、前端banner轮播图文本均已进行可视化配置 4、伪静态
立即学习“go语言免费学习笔记(深入)”;
- 函数适配器本质是类型别名 + 方法绑定,例如
type FuncWriter func([]byte) (int, error),再给它实现Write方法 - 闭包写在初始化位置更清晰,比如
writer := FuncWriter(func(b []byte) (int, error) { return sendEmail(string(b)) }) - 注意:如果原函数参数和目标接口不匹配(比如要
string,接口要[]byte),转换必须显式写出,Go 不会自动帮你转 - 不适合带状态的场景(比如需要维护连接池、重试次数),因为函数值本身无字段
适配第三方 API 时的关键细节
对接阿里云短信、腾讯云短信、钉钉 Webhook 这类服务,它们的 SDK 方法签名往往五花八门,适配器就是统一入口的“胶水层”。
- 每个服务商单独写一个适配器(
AliyunSMSAdapter、TencentSMSAdapter),都实现同一个Notifier接口 - 参数映射由适配器完成:比如阿里云要
SignName和TemplateCode,而你的业务只传message,就在适配器里填默认值或查配置表 - 错误处理也在这里做归一化:把
aliyun.ErrSignatureInvalid转成通用的ErrNotifyFailed,避免上层到处判断不同错误类型 - 不要在适配器里做重试或熔断——那是中间件或调用方的事;适配器只保证“能调通”,返回值和错误类型严格匹配接口定义
容易忽略的坑:空指针、接收者类型、循环依赖
适配器看着简单,但上线后最容易爆的不是逻辑错,而是这几个隐性问题:
-
nil指针 panic:如果适配器字段是*LegacyLogger,但初始化时传了nil,又没在Log方法里判空,运行时直接崩溃;建议在构造函数里加非空检查,或方法内加if a.logger == nil { return } - 接收者类型不一致:被适配对象方法用的是值接收者(
func(l LegacyLogger) Log()),但你在适配器里用指针字段(logger *LegacyLogger)去调,会报错;保持接收者类型一致,或统一用指针 - 循环依赖:适配器包 import 了被适配类型所在包,而那个包又 import 了适配器(比如为了测试),Go 编译直接失败;解法是把适配器放在调用方包里,或新建一个
adapters包,只依赖接口,不依赖具体实现
真正难的不是写适配器,而是判断“哪里该加一层”。只要看到编译报错说 does not implement X (missing Y method),而你又不能改那个类型——那就该动手写了。









