Adapter 适配器也是属于“接口隔离”模式,也是间接思想在某一个层面的应用。
文章目录
- 1. 动机( Motivation)
- 2. 模式定义
- 3. 结构 (Structure)
- 4. Adapter 适配器的代码实现
- 5. 要点总结
- 6. 其他参考
1. 动机( Motivation)
- 在软件系统中,由于应用环境的变化,常常需要将
“一些现存的对象”
放在新的环境中应用
,但是新环境要求的接口是这些现存对象所不满足的。 - 如何应对这种
“迁移的变化”
?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
适配器模式是非常久远的模式,来源于我们的生活,以下是常见的适配器,适配器的意思对接不同接口的匹配需求。
适配器的思想应用到类的设计中。
2. 模式定义
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
----《设计模式》GoF
3. 结构 (Structure)
Target 是未来的也就是希望的接口,Adaptee被适配者是以前的,遗留的接口,我们都希望他们保持不变,但是我们怎么将被适配的接口应用到新的环境,就需要具体的Adapter,Adapter类是继承自Target,我们在学很多模式的时候,常常强调,继承一个类,实际上表明我遵循基类定义的接口规范,在这里也就是Adapter具有Target一样的接口规范。同时可以看到有一个,同时可以看到一个组合,从 Adapter伸出一个箭头指到Adaptee。组合一个类就是支持一个实现的方式, Adapter就实现了把Adaptee往Target的转换。
上图是《设计模式》GoF中定义的Adapter 适配器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
在具体实现过程中,Adapter 是千变万化的。
4. Adapter 适配器的代码实现
//目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遗留类型
class OldClass: public IAdaptee{
//....
};
//对象适配器
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
public:
//构造器
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//类适配器
class Adapter: public ITarget,
protected OldClass{ //多继承
}
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
ITarget
是目标接口,IAdaptee
是遗留接口,由于某种内在的实现层面的关联性,适配器得能关联,可以把IAdapter
转成一个ITarget
。
Adapter: public ITarget
中Adapter继承自ITarget接口,就是遵循其定义的规范。实现virtual void process()=0;
,在实现的过程中可能得转换过程如下:
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
实际上真正的转换过程在某些平台上是非常复杂的,不像伪码表示的那么简单。第一要存在可转换,第二转换过程是相当复杂的。实际过程中可能类似process()
的接口是比较多的,不像此处只有一个,通常可能是其他几个接口联合起来变成其中一个接口。
IAdaptee有一些老的类OldClass是继承自IAdaptee符合老的接口的。
真正在使用时代码如下:
int main(){
IAdaptee* pAdaptee=new OldClass();
ITarget* pTarget=new Adapter(pAdaptee);
pTarget->process();
}
从此可以拿着一个旧的类 IAdaptee* pAdaptee=new OldClass();
塞到Adapter里面ITarget* pTarget=new Adapter(pAdaptee);
,当做一个新的类型,面向新接口的实现去使用。
在现实实现过程中,有一些细节可能不一样,比如stl中stack和queue的内部其实都使用了一个deque这样的一个内部的pAdaptee对象,它们都实现了它们的接口转成另外一个接口,那里可能没有明确的感觉谁是新的谁是旧的,只是说利用老的接口deque,转成stack和queue要求的接口,实际上是一个接口转换。那个里面是没有用指针,是直接将deque对象放进去,简单写成伪码就是如下:
class stack{
deqeue container;
};
class queue{
deqeue container;
};
这种情况下,你可能没看到stack的接口是什么,因为stack本身既是接口又是实现,queue也是如此,但其总体符合Adapter设计模式的宗旨,可以将老的接口转成新的接口,所以我们很多时候也把它称为Adapter设计模式。
我们常常讲,大家在学习设计模式过程中,你不能太死板,不一定要追求GoF经典定义一模一样的代码。设计模式会有变体模式,不一定和经典的一致。但是最关键的是这个设计模式的场景,和它解决问题的手法,以及对变化点和稳定点的分离方式是不是符合模式的定义。
5. 要点总结
- Adapter模式主要应用于
“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”
,在遗留代码复用、类库迁移等方面非常有用。 - GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用。对象适配器采用
“对象组合”
的方式,更符合松耦合精神。
上面所讲的是对象适配器,主要区别点是组合了一个对象
//对象适配器
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
};
也有一些采用类适配器,主要区别点是多继承
//类适配器
class Adapter: public ITarget,
protected OldClass{ //多继承
}
使用继承OldClass带来的问题就是定死在OldClass,而没有灵活性(继承没有灵活性),而采用组合方式是十分具有灵活性的,IAdaptee* pAdaptee;
可以指向OldClass,也可以指向AnotherClass
- Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。
6. 其他参考
C++设计模式——适配器模式