Go中适配器模式通过组合+显式方法转发实现:用struct包装旧接口并实现新接口,或用函数类型实现目标接口,需手动处理参数转换、错误映射和context传递,禁用反射或空接口。

适配器模式在 Go 中没有接口继承,怎么办
Go 语言不支持传统面向对象的接口继承,所以不能像 Java 那样让一个接口 extends 另一个接口。适配器的核心目标——让不兼容的接口协同工作——必须靠组合 + 显式方法转发实现,而不是语法层面的“转换”。
关键点在于:适配器不是类型转换器,而是包装器。它持有旧接口的实例,并暴露新接口的方法,内部调用旧接口对应逻辑。
- 不要试图写
func (a *Adapter) Convert() NewInterface这类“转换函数”——Go 没有隐式转换,也没有运行时接口重铸 - 适配器类型必须显式实现目标接口的所有方法,哪怕只是透传或简单封装
- 如果旧接口方法签名和新接口不一致(比如参数多一个 context,或返回值顺序不同),必须在适配器里做参数调整、错误映射等逻辑处理
用 struct 包装旧接口并实现新接口
这是最常见、最清晰的适配方式。例如,你有一个老服务返回 LegacyService 接口,但新业务需要 UserService 接口:
type LegacyService interface {
GetUserByID(id string) (string, error) // 返回 name
}
type UserService interface {
GetUserInfo(ctx context.Context, id int) (*User, error)
}
type User struct {
ID int
Name string
}
type LegacyAdapter struct {
legacy LegacyService
}
func (a *LegacyAdapter) GetUserInfo(ctx context.Context, id int) (*User, error) {
// 调用旧接口:id 类型不匹配,需转换;返回值结构不同,需构造
name, err := a.legacy.GetUserByID(strconv.Itoa(id))
if err != nil {
return nil, err
}
return &User{ID: id, Name: name}, nil
}
注意:LegacyAdapter 本身不“变成”UserService,但它实现了该接口,因此可直接赋值给 UserService 类型变量。
立即学习“go语言免费学习笔记(深入)”;
避免空接口或反射做“通用适配”
有人尝试用 interface{} 或 reflect.Value.Call 写“自动适配器”,这在 Go 中既不可靠也不必要:
-
interface{}丢失类型信息,无法静态校验方法签名,编译期零保护 - 反射调用性能差,且无法处理参数类型转换(比如
int→string)、错误包装、context 注入等真实适配逻辑 - 一旦旧接口加方法或改签名,反射适配器完全失效,而结构体适配器会在编译时报错,问题暴露得早
适配逻辑越简单,越要手写;越复杂,越要分层(比如先做数据模型转换,再包一层服务接口)。
HTTP handler 适配是高频使用场景
把自定义 handler 适配成 http.Handler 是典型例子。假设你有个函数签名是 func(string) string,想塞进 HTTP 路由:
type StringHandler func(string) string
func (fn StringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
result := fn(r.URL.Path)
fmt.Fprint(w, result)
}
// 使用:
http.Handle("/greet", StringHandler(func(path string) string {
return "Hello from " + path
}))
这里 StringHandler 是一个函数类型,它通过实现 ServeHTTP 方法“适配”成了标准 http.Handler。这种基于函数类型的适配轻量又常见,比 struct 更简洁。
真正容易被忽略的是上下文传递和错误处理——比如原函数可能返回 error,但 ServeHTTP 不能直接返回,必须转成 HTTP 状态码和响应体,这部分逻辑必须显式写进适配器里,没法省。










