设计模式系列(十)适配器模式(Adapter Pattern)
适配器模式是将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。适配器实现了客户与具体实现的解耦,简单来说,适配器模式就是改变接口以符合客户的期望。例如:我们生活中常见的插座的适配器、无线网卡适配器、电源适配器等,这些都是用来进行不同接口的转换,更具体的来说就是我们手机充电的时候可能只需要5V的电压,而插座里面提供的交流电是220V的,那么我们通过一系列的适配器将电压转化为5V以供我们手机充电使用。
适配器模式中的角色主要有:
(1)目标角色(Target):这个角色就是客户所期望得到的,例如上例中的5V电压就是客户期望得到的目标角色;
(2)被适配角色(Adaptee):这个角色也很好理解,就是需要被适配成目标角色的角色,例如上例中的220V电压;
(3)适配器角色(Adapter):这个角色就是一个中间者,用来将被适配角色转换成目标角色,相当于是一个中间处理的环节。
适配器模式的使用前提是:接口中规定了所有要实现的方法;一个要实现此接口的具体类可能只用到了其中的几个方法,而其它的方法都是没有用的。需要注意的是,适配器模式在通常的例子中都是只适配一个类,但是这并不意味着适配器模式只能用来适配一个类,当有需求的时候,可以适配多个类,所以大家不要误以为适配器模式只能用来适配一个类。
适配器主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,在遗留代码复用、类库迁移等方面非常有用,比如JAVA中的一些以前的接口要想在新的JDK中使用,就可以通过适配器模式来转换接口,实现兼容性。总而言之,适配器模式的使用场景主要是:
(1)系统需要使用现有的类,而这些类的接口不符合系统的接口;
(2)想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作;
(3)两个类所做的事情相同或相似,但是具有不同接口的时候;
(4)旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候;
(5)使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
适配器模式的优点是:
(1)通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
(2)复用了现存的类,解决了现存类和复用环境要求不一致的问题。
(3)将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
(4)一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
适配器模式的缺点是:对于对象适配器来说,更换适配器的实现过程比较复杂。
适配器模式的实现方式有多种,一般可分为类适配器与对象适配器,还有一种特殊的缺省适配器。
类适配器是通过多重继承的方式来实现,由于有些语言不支持多重继承,并且有一个设计原则建议多用组合少用继承,所以类适配器并不推荐使用,但是类适配器由于是使用继承,所以可以通过适配器来覆盖被适配者的函数,从而不需要重新实现整个被适配者;对象适配器是通过组合的方式来实现,所以比较常见,其弹性较好;缺省适配器很少见,主要用于仅仅实现感兴趣的函数。
下面我们来看一下一个例子,该例由三个文件组成,依次是:AdapterPattern.h、AdapterPattern.cpp、AdapterPatternTest.cpp。
// 适配器模式
// AdapterPattern.h文件
#ifndef ADAPTER
#define ADAPTER
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
using std::string;
using std::cout;
using std::endl;
using std::vector;
// 适配器模式中客户需要的目标对象的抽象类
class Duck
{
public:
Duck(){}
virtual ~Duck(){}
virtual void quack() = 0;
virtual void fly() = 0;
};
// 适配器模式中被适配对象的抽象类
class Turkey
{
public:
Turkey(){}
virtual ~Turkey(){}
virtual void gobble() = 0;
virtual void fly() = 0;
};
// 适配器模式中客户需要的目标对象的具体类
class MallardDuck : public Duck
{
public:
void quack();
void fly();
};
// 适配器模式中被适配对象的具体类
class WildTurkey : public Turkey
{
public:
void gobble();
void fly();
};
// 类适配器
class TurkeyClassAdapter : public Duck, public WildTurkey
{
public:
void quack();
void fly();
};
// 对象适配器
class TurkeyObjectAdapter : public Duck
{
public:
TurkeyObjectAdapter(Turkey *turkey)
{
this->turkey = turkey;
}
void quack();
void fly();
private:
Turkey *turkey;
};
#endif
// AdapterPattern.cpp文件
#include "AdapterPattern.h"
// 适配器模式中客户需要的目标对象的具体类
void MallardDuck::quack()
{
cout << "Quack" << endl;
}
void MallardDuck::fly()
{
cout << "I'm flying" << endl;
}
// 适配器模式中被适配对象的具体类
void WildTurkey::gobble()
{
cout << "Gobble gobble" << endl;
}
void WildTurkey::fly()
{
cout << "I'm flying a short distance" << endl;
}
// 类适配器
void TurkeyClassAdapter::quack()
{
gobble();
}
void TurkeyClassAdapter::fly()
{
// 由于火鸡飞行距离较短,而鸭子需要较长的飞行距离
// 所以这里飞行五次
for (int i = 0; i < 5; i++)
{
WildTurkey::fly();
}
}
// 对象适配器
void TurkeyObjectAdapter::quack()
{
turkey->gobble();
}
void TurkeyObjectAdapter::fly()
{
// 由于火鸡飞行距离较短,而鸭子需要较长的飞行距离
// 所以这里飞行五次
for (int i = 0; i < 5; i++)
{
turkey->fly();
}
}
// AdapterPatternTest.cpp文件
#include "AdapterPattern.h"
void main()
{
// 类适配器测试
cout << "------------------------------------------" << endl;
TurkeyClassAdapter turkeyClassAdapter;
turkeyClassAdapter.quack();
turkeyClassAdapter.fly();
// 对象适配器测试
cout << "------------------------------------------" << endl;
Turkey *tukey = new WildTurkey();
TurkeyObjectAdapter turkeyObjectAdapter(tukey);
turkeyObjectAdapter.quack();
turkeyObjectAdapter.fly();
delete tukey;
tukey = NULL;
cout << "------------------------------------------" << endl;
}
该例的运行结果如图1所示,UML类图如图2所示。
图1 运行结果
图2 UML类图
该例中包含了类适配器和对象适配器两种实现方式,其中TurkeyClassAdapter类是类适配器,通过图2可以看出该适配器继承了Duck类和Turkey类,即多重继承;而TurkeyObjectAdapter类是对象适配器,通过图2可以看出该适配器继承了Duck类,其中包含了一个Turkey的指针,用来指向一个具体的Turkey对象,即组合。通过图1的运行结果可以看出这两种实现方式的运行结果是一样的。
该例的主要意思是顾客希望得到一个Duck,但是目前只有Turkey的具体实现和Duck的接口,所以只能通过适配器将Turkey包装一下,使其看起来就是一个Duck,从而客户看到的还是Duck,而实际上是Turkey,即实现了接口转换。
最后给出一个网上十分常见的缺省适配器的例子,了解一下即可,注意其中只是实现了自己感兴趣的函数f3()。
#include<iostream>
using namespace std;
class Target {
public:
virtual void f1(){};
virtual void f2(){};
virtual void f3(){};
};
class DefaultAdapter : public Target
{
public:
void f1() {
}
void f2() {
}
void f3() {
}
};
class MyInteresting :public DefaultAdapter
{
public:
void f3(){
cout<<"呵呵,我就对f3()方法感兴趣,别的不管了!"<<endl;
}
};
int main()
{
// Create adapter and place a request
Target *t = new MyInteresting();
t->f3();
return 0;
}
总之,适配器模式还是很用的,通常使用对象适配器。