go中适配器模式需包装第三方结构体并显式实现接口方法,因无继承机制;必须持有原对象引用、严格匹配方法签名、透传context、处理错误映射及边界情况。

为什么 Go 里适配器模式不是“写个 interface 就完事”
因为 Go 没有继承,也没有“实现某个抽象类”的机制,适配器的本质是让一个类型满足另一个接口的契约,而不是改造它本身。第三方库(比如 github.com/aws/aws-sdk-go-v2/service/s3)返回的结构体往往不直接实现你定义的 ObjectStorer 接口,硬套会报错:cannot use s3Client (type *s3.Client) as type ObjectStorer。
这时候不能改 SDK 源码,也不能强转——得靠包装(wrapper)加方法转发。
- 适配器必须持有原对象引用(如
*s3.Client),否则无法调用其方法 - 所有被适配的方法,必须显式实现,不能靠嵌入自动满足(除非嵌入的是接口类型,但第三方结构体不是)
- 注意方法签名完全一致:参数名可不同,但类型、顺序、返回值必须严格匹配
怎么写一个最小可行的 S3 适配器
假设你已有业务接口:type ObjectStorer interface { Put(ctx context.Context, key string, data io.Reader) error },而 AWS SDK 的 s3.PutObject 需要 *s3.PutObjectInput 和显式 io.ReadSeeker。
适配器要做三件事:封装 client、转换参数、处理错误映射。
立即学习“go语言免费学习笔记(深入)”;
- 定义适配器结构体:
type S3Adapter struct { client *s3.Client } - 实现
Put方法时,把io.Reader包装成io.NopCloser或用bytes.NewReader转成io.ReadSeeker(S3 SDK 要求 seekable) - 错误不能直接返回
err,得检查是否为*smithy.OperationError或*s3types.NotFoundException,再转成你自己的错误类型
func (a *S3Adapter) Put(ctx context.Context, key string, data io.Reader) error {
b, _ := io.ReadAll(data)
_, err := a.client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String(key),
Body: bytes.NewReader(b),
})
if err != nil {
return fmt.Errorf("s3 put failed: %w", err)
}
return nil
}
别忽略 Context 传递和超时控制
第三方客户端(如 *s3.Client)内部依赖 context.Context 做请求取消和超时,但你的适配器如果在方法里新建了 context(比如 context.WithTimeout(context.Background(), ...)),就切断了上游调用链的 cancel 信号。
- 必须把传入的
ctx直接透传给底层 client 方法,不能替换 - 如需加超时,用
context.WithTimeout(ctx, time.Second*30),而非context.WithTimeout(context.Background(), ...) - SDK 日志或 trace ID 也依赖原始 ctx 中的
values,替换后可能丢失可观测性信息
测试适配器时最容易漏掉的边界
真实环境里,io.Reader 可能是大文件流、网络响应体、甚至 nil。适配器若没做防御性检查,上线后遇到 nil 就 panic。
- 在
Put开头加if data == nil { return errors.New("data is nil") } - 用
io.LimitReader控制上传大小上限,避免 OOM(尤其当data是未限制的 HTTP body) - Mock 测试时别只 mock 成功路径,要覆盖
io.ErrUnexpectedEOF、context.Canceled等中断场景
适配器看着简单,但每层转发都在放大调用链的脆弱点——参数转换、错误归一、context 透传、资源清理,少一个都容易在线上突然失效。










