在Golang项目里,我们经常会遇到这样的状况:已有功能模块的接口和当前系统要求的接口对不上,直接改原代码成本又高。这时候适配器模式就派上用场了。Go语言没有传统面向对象里的类继承,所以类适配和对象适配的实现思路和Java等语言不太一样,主要靠结构体嵌入和字段组合来完成。

什么是适配器模式
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法一起工作的类可以协同工作。在Golang中一般涉及三个角色:
- Target:客户端期望的接口
- Adaptee:需要被适配的已有实现
- Adapter:适配器本身
类适配在Golang中的实践
Go没有继承,所谓类适配通常是指通过结构体嵌入Adaptee类型,让Adapter同时拥有Adaptee的方法,并实现Target接口。下面来看示例。
定义目标接口与被适配者
package main
import "fmt"
// Target 客户端期望的接口
type Target interface {
Request() string
}
// Adaptee 已有实现,方法名不同
type Adaptee struct{}
func (a *Adaptee) SpecificRequest() string {
return " Adaptee的特定请求结果"
}
类适配实现
// ClassAdapter 通过嵌入Adaptee实现类适配
type ClassAdapter struct {
*Adaptee
}
func (c *ClassAdapter) Request() string {
// 调用嵌入类型的已有方法
return "类适配:" + c.SpecificRequest()
}
func main() {
var t Target = &ClassAdapter{Adaptee: &Adaptee{}}
fmt.Println(t.Request())
}
这里ClassAdapter嵌入了*Adaptee,从而直接具备SpecificRequest能力,并补上Request方法满足Target。
对象适配在Golang中的实践
对象适配是指Adapter不嵌入Adaptee,而是把Adaptee作为普通字段持有,通过调用该字段的方法来完成适配。这种方式更灵活,也能适配多个不同的被适配对象。
对象适配实现
// ObjectAdapter 持有Adaptee字段实现对象适配
type ObjectAdapter struct {
adaptee *Adaptee
}
func NewObjectAdapter(a *Adaptee) *ObjectAdapter {
return &ObjectAdapter{adaptee: a}
}
func (o *ObjectAdapter) Request() string {
return "对象适配:" + o.adaptee.SpecificRequest()
}
func main() {
adaptee := &Adaptee{}
var t Target = NewObjectAdapter(adaptee)
fmt.Println(t.Request())
}
两种适配方式怎么选
如果你的Adaptee本身是一个明确类型,且以后基本不会变,类适配写起来更紧凑;如果你希望一个Adapter能包装不同的Adaptee实例,或者Adaptee可能有多重来源,对象适配更合适。可以用下面这个表快速对照:
| 对比项 | 类适配 | 对象适配 |
|---|---|---|
| 实现方式 | 嵌入被适配类型 | 持有被适配字段 |
| 灵活性 | 较低 | 较高 |
| 多适配源 | 不支持 | 支持 |
小结
在Golang里用适配器模式并不复杂,核心就是利用嵌入做类适配、利用字段组合做对象适配。实际开发中建议优先考虑对象适配,因为它的耦合度更低,也更符合Go的组合思想。当接口改造需求出现时,选对方式能让你少写很多胶水代码。