适配器模式
什么是适配器
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
适配器模式的分类:
- 类适配器模式:通过多重继承机制,将Adaptee类适配到Target接口。
- 对象适配器模式:通过持有一个Adaptee对象的实例,将Adaptee类适配到Target接口。
基本流程
- 定义目标接口(Target):
- 确定你的应用程序需要的接口(即目标接口),这个接口定义了应用程序期望的交互方式。
- 识别被适配者(Adaptee):
- 找到一个已经存在的类或对象(被适配者),它实现了一些功能,但接口与目标接口不兼容。
- 创建适配器类(Adapter):
- 创建一个适配器类,它实现目标接口,并在内部维护一个被适配者的实例。
- 实现转换逻辑:
- 在适配器类中,实现目标接口定义的方法,并通过内部的被适配者实例来执行相应的操作,实现接口的转换。
- 客户端调用:
- 客户端代码通过目标接口与适配器对象交互,适配器对象将请求转换为被适配者可以理解的格式,并调用被适配者的方法。
- 适配器对象的实例化:
- 在客户端代码中,创建适配器对象的实例,并将被适配者对象传递给适配器。
- 使用适配器:
- 客户端通过适配器对象来访问被适配者的功能,无需关心被适配者的接口细节。
结构
适配器模式的组成:
- Target(目标接口):定义客户端使用的特定领域相关的接口。
- Adaptee(被适配者):一个已经存在的类,需要适配。
- Adapter(适配器):通过在内部包装一个Adaptee对象,把源接口转换成目标接口。
优点
- 兼容性:可以让不兼容的接口能够一起工作。
- 解耦:适配器模式将目标接口与被适配者解耦,使得它们可以独立变化,只需保证适配器的实现正确即可。
- 灵活性:增加了新的接口不需要修改原有代码,符合开闭原则。即对扩展开放,对修改封闭。
缺点
• 增加系统的复杂度:每增加一个需要适配的类,都需要增加一个适配器。
使用场景
- 接口不兼容:当需要使用一个类,但这个类的接口与当前系统的接口不兼容时。
- 第三方库:需要与第三方库集成,但第三方库的接口不符合系统要求时。
- 保持接口独立:希望在不修改现有接口的情况下,增加新的功能或行为。
- 对象组合:希望将一个或多个不兼容的对象组合到一个统一的接口下。
注意事项
- 适配器数量:考虑系统是否能够承受引入多个适配器带来的复杂性。
- 适配器职责:适配器应该保持职责单一,只关注于接口的转换。
代码案例
类适配器模式
类适配器模式使用继承来实现适配器。在 Go 语言中,由于不支持多重继承,我们通常不使用类适配器模式。
组合优于继承:Go 鼓励使用组合(composition)而不是继承(inheritance)来实现代码复用。
- 继承:是一种静态的关系,子类在编译时就确定了其基类。继承是一种强耦合关系,子类依赖于基类的实现细节。
- 组合:是一种动态的关系,一个对象可以在运行时动态地包含另一个对象。组合是一种弱耦合关系,对象之间的依赖仅仅是接口。
对象适配器模式
对象适配器模式通过组合来实现适配器,这是 Go 语言中更常见的适配器模式实现方式。
简单案例
// Target 接口
func Target interface{
Request()
}
// Adaptee 接口
type Adaptee struct{}
func(a *Adaptee)SpecificRequest(){
fmt.Println("Adaptee specific request")
}
// 对象适配器
type OjectAdapter struct{
adaptee *Adaptee
}
func (o *OjectAdapter)Request(){
o.adaptee.SpecificRequest()
}
func main() {
adaptee := adapter.Adaptee{}
// 实例化适配器,并传入被适配者对象
objectAdapter := adapter.ObjectAdapter{&adaptee}
objectAdapter.Request() // 使用对象适配器模式
}
复杂案例
// 美国插座接口
type USASocket interface {
PlugIn()
}
// 英式插头
type BritishSocket struct{}
func (b *BritishSocket) PlugIn() {
fmt.Println("Plug into British socket")
}
// 欧式插头
type EuropeanSocket struct{}
func (e *EuropeanSocket) Insert() {
fmt.Println("Insert into European socket")
}
// 适配器
type SocketAdapter struct {
europeanSocket *EuropeanSocket
}
func (sa *SocketAdapter) PlugIn() {
sa.europeanSocket.Insert()
}
// 在使用美国插座时,无需区分插头类型,统一使用USASocket接口
func useUSASocket(socket USASocket) {
socket.PlugIn()
}
// 客户端代码
func main() {
britishSocket := &BritishSocket{}
europeanSocket := &EuropeanSocket{}
adapter := &SocketAdapter{
europeanSocket: europeanSocket,
}
// 使用适配器将欧式插头适配成美式插座
useUSASocket(britishSocket)
useUSASocket(adapter)
}
我们定义了USASocket
接口作为统一的充电器接口,BritishSocket
和EuropeanSocket
分别代表英式和欧式插头接口。然后,我们创建了一个SocketAdapter
适配器,将EuropeanSocket
适配成了USASocket
接口。在main
函数中,我们先创建了一个英式插头对象和一个欧式插头对象,然后使用适配器将欧式插头适配成了统一的美式插座接口。