Adapter其实来源于我们的生活
- 水管的适配器,解决两头的水管的大小不匹配
- 把HDVI的信号转成VGA的信号
- 电源适配器,把220V的电压变成更低的电压
生活中的适配器非常常见,它其实在对接不同接口之间的匹配需求
动机
在软件系统中,由于应用环境的变化,尝尝需要将“一些现存的对象”放在新的环境中应用,但是新的环境要求的接口时这些现存对象所不满足的。
如何应对这种“迁移的变化”?如果既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
模式定义
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。——《设计模式》GoF。
结构
- Target是未来的接口,我们希望的接口
- Adaptee被适配者,是我们以前的接口
- 我们都希望Target、Adaptee保持不变,我们怎么做才能把Adaptee应用到新的环境中(Target)?
- Adapter继承Target,即Adapter具有Target一样的接口规范,is-a
- Adapter组合Adaptee,即支持一个实现的方式,has-a
- 这样就实现了Adaptee到Target的转换
附:
- 继承一个类,实际上在表明,我遵守你基类定义的一个接口规范
代码
//目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遗留类型(老接口的具体类)
class OldClass: public IAdaptee{
//....
};
//////////////////////////////////////////////////把IAdaptee转成ITarget
//第一种适配器:对象适配器(通过组合对象来实现的)
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//第二种适配器:类适配器(通过多继承实现)
//有许多扩展的问题
//1. 继承的是oldclass,就定死在这个类上了,没有灵活性
class Adapter: public ITarget, protected OldClass{ //多继承
}
int main(){
IAdaptee* pAdaptee=new OldClass(); //老接口
ITarget* pTarget=new Adapter(pAdaptee); //一个适配器
pTarget->process();
}
在STL中也是如此,stack和queue内部使用的都是deque数据结构
- 利用老的接口(deque),转成一个新的接口stack ```cpp class stack{ deqeue container;
};
class queue{ deqeue container;
}; ```
要点总结
Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类迁移等方面非常有用。
GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器
- 但类适配器采用“多继承”的实现方式,一般不推荐使用
- 对象适配器采用“对象组合”的方式,更符合松耦合的精神
Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构
- 例如:完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的