一、适配器(Adapter,别名“包装器”Wrapper)
1、引言
假设你手头有一个很好用的第三方库资源(只有接口,无源代码)或者已经有一个独立性比较强的单独工具箱,现在一个项目中面向特定领域的应用中要用到上述资源,可是接口不兼容。这时可以考虑适配器模式,设计适配器(Adaper)将资源类接口(Adaptee)适配成应用中的接口(Target)。
2、一般思路
类适配器采用多重继承,图中的“实现继承”是为了与“接口继承”区别,表明子类Adapter只继承其父类Adaptee的实现部分,而不把其接口对外开放。在C++中的public继承既是接口继承又是实现继承。可通过private继承获得实现继承的效果(private继承后,父类中的接口都变为private,当然只能是实现继承)。
客户在Adapter中调用一些操作(如Request()),适配器会调用Adaptee的操作(如SpecificRequest())来实现该请求。
3、典型代码
一、类适配器:
(1)在Adapter类声明:
classAdapter : public Target, private Adaptee {… voidRequest(); …}
(2)在Request()实现中,this->SpecificRequest();
二、对象适配器:
(1)在Adapter类声明:
classAdapter : public Target
{
public:
Adapter(Adaptee*ade);
voidRequest();
……
private:
Adaptee*_ade;
}
(2)在构造函数Adapter(Adaptee*ade)实现中,this->_ade = ade;
(3)在Request()实现中,_ade->SpecificRequest();
(4)在客户代码main()中:
Adaptee*ade = new Adaptee;
Target*adt = new Adapter(ade);
adt->Request();
4、应用提示
(1)对于对象适配器,允许一个Adapter与多个Adaptee(即Adaptee与它的子类)同时工作。Adapter可一次给所有Adaptee添加功能。
(2)Adapter的工作量取决于Target与Adaptee的相似度。
(3)不同系统对于一个组件Adaptee的接口可能不同,可通过三种途径来实现可插入的Adapter,使组件具有接口适配功能:使用抽象操作;使用代理对象;参数化。
二、装饰(Decorator,别名“包装器”Wrapper)
1、引言
当你想为某个对象A而非整个类添加一些功能时,并且不修改A中任何底层代码,可以考虑为这个对象A中嵌入另一个对象B,由B来专门负责装饰。当你需要调用A的某个功能operation()时,只需要创建B(动态,“即付即用”),然后依旧调用operation()即可(当然,你需要让B和A具备同样的接口名称,并在实现中添加特定的装饰内容)。一般,你(客户)不会感觉到装饰前后的差异(透明性),也不会对装饰物B产生任何依赖。
2、一般思路
如下图,被装饰对象Component(及其子类)和装饰Decorator(及其子类)有共同的接口operation()。装饰Decorator需要包含一个指向Component对象的指针。
3、典型代码
结合《Head first》中的“咖啡”实例,按照上图类的关系,编写实例完整代码如下:
// decorator.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "iostream"
#include "string"
#include <conio.h>
using namespace std;
class Component
{
public:
string description;
Component(){
description = "unknowed component";
}
virtual ~Component(){}
virtual double Operation() = 0;
virtual string getDescrition(){
return description;}
};
class ConcreteComponent1 : public Component
{
public:
ConcreteComponent1(){
description = "ConcreteComponent1";
}
~ConcreteComponent1(){}
double Operation(){
return 1100;
};
};
class ConcreteComponent2 : public Component
{
public:
ConcreteComponent2(){
description = "ConcreteComponent2";
}
~ConcreteComponent2(){}
double Operation(){
return 2200;
}
};
class Decorator : public Component
{
public:
Decorator(Component* com){
this->_com = com;
}
virtual ~Decorator(){
delete _com;
}
double Operation(){return 0;}
protected:
Component* _com;
};
class ConcreteDecorator1 : public Decorator
{
public:
ConcreteDecorator1(Component* com):Decorator(com){}
virtual ~ConcreteDecorator1(){}
double Operation(){
return 0.11+_com->Operation();
};
string getDescrition(){
return _com->getDescrition()+",ConcreteDecorator1";
}
};
class ConcreteDecorator2 : public Decorator
{
public:
ConcreteDecorator2(Component* com):Decorator(com){}
virtual ~ConcreteDecorator2(){}
double Operation(){
return 0.22+_com->Operation();
}
string getDescrition(){
return _com->getDescrition()+",ConcreteDecorator2";
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Component* com1 = new ConcreteComponent1();//产品1
com1 = new ConcreteDecorator1(com1);
com1 = new ConcreteDecorator2(com1);
cout<<com1->getDescrition()<<" == "<<com1->Operation()<<endl;
Component* com2 = new ConcreteComponent2();//产品2
com2 = new ConcreteDecorator1(com2);
com2 = new ConcreteDecorator1(com2);
com2 = new ConcreteDecorator2(com2);
com2 = new ConcreteDecorator2(com2);
cout<<com2->getDescrition()<<" == "<<com2->Operation()<<endl;
delete com1,com2;
_getch();/*等待按键继续*/
return 0;
}
(1)在ConcreteDecorator2类的Operation()中 return 0.22+_com->Operation(); 实际相当于书中提到的AddedBehavior(),即在Component类的Operation()(_com-> Operation())基础上增加了操作予以修饰!
(2)在程序_tmain()中,客户只需要知道被修饰对象ConcreteComponent1/2和装饰者ConcreteDecorator1/2的具体名称,调用统一接口Component即可完成操作。
(3)代码运行情况如下:1100+0.22+0.11 = 1100.33; 2200+0.11*2+0.22*2 = 2200.66
4、应用提示
(1)为使所有装饰对象和Componet对象有相同的接口,需要使用Decorator作为各个装饰对象的父类。当然,你也可以人工去保证接口统一,从而省掉Decorator类。
(2)装饰模式是“改变对象外壳”,今后关注策略Strategy模式用以“改变对象内核”。
(3)装饰模式仅改变对象的职责,而适配器Adapter则为对象提供全新的接口”。
(4)可以将装饰模式视为一个“退化的,仅有一个组件的组合Composite”。
(5)《Head first》引入一个重要的设计原则:“类应该对扩展开放,对修改关闭。”(简称“开放-关闭原则”)。需要把注意力集中在最可能改变的地方,然后应用该原则。
(6)装饰模式的一个负面作用是,需要构建和管理大量的装饰对象。但它们一般是用工厂或生成器来创建(期待实例),会“封装得非常好”,故不用太过担心。