设计模式系列(十)适配器模式(Adapter Pattern)
2015-12-11 19:45
549 查看
设计模式系列(十)适配器模式(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; }
总之,适配器模式还是很用的,通常使用对象适配器。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- PropertyChangeListener简单理解
- 关于指针的一些事情
- 什么是设计模式
- 设计模式之创建型模式 - 特别的变量问题
- 七、设计模式——装饰模式
- 设计模式总结
- 设计模式之创建型模式
- 浅谈设计模式的学习
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题