一、什么是适配器模式 在我们平时的开发过程中,碰到要在两个完全没有关系的类之间进行交互,第一个解决方案是修改各自类的接口,但是如果无法修改源代码或者其他原因导致无法更改接口,此时怎么办?这种情况我们往往会使用一个 Adapter ,在这两个接口之间创建一个粘合剂接口,将原本无法协作的类进行兼容,而且不用修改原来两个模块的代码,符合开闭原则。
定义:适配器模式属于结构型模式,把一个类的接口变成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。
适配器模式又可以分为4种类型,类适配器模式、对象适配器模式、单接口适配器模式(缺省适配器模式)和双向适配器模式。后2种模式的实现比较复杂并且在实际开发过程中很少使用。
二、适配器模式的结构
由图可知适配器模式包含一下三个角色:
1:Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于C#语言不支持多重继承,所以它只能是接口。
2:Adapter(适配器类):它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。它是适配器模式的核心。
3:Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类包好了客户希望的业务方法。
三:适配器模式的实现 1:类适配器
class Adapter : public Adaptee, public Target { public: void Request() { Adaptee::SpecificRequest(); } }2:对象适配器
#include <iostream> using namespace std; /********************************** Target,客户期望的接口, 可以使具体或抽象的类,也可以是接口 **********************************/ class Target { public: virtual void Request() = 0; virtual ~Target(){}; }; // 需适配的类 class Adaptee { public: void SpecificRequest() { cout << "Adaptee" << endl; } }; // 通过内部包装一个Adaptee对象,把源接口转换为目标接口 class Adapter : public Target { private: Adaptee* adaptee; public: Adapter() { adaptee = new Adaptee(); } // 调用Request()方法会转换成调用adaptee.SpecificRequest() void Request() { adaptee->SpecificRequest(); } ~Adapter() { delete adaptee; } }; int main() { Target* target = new Adapter(); target->Request(); delete target; while(1); return 0; } #include<iostream> #include<string> class Player // Target,定义用户实际需要的接口。 { protected: std::string m_name; public: virtual ~Player(){}; explicit Player(std::string name) :m_name(name) {} virtual void Attack() = 0; // 攻击 virtual void Defense() = 0; // 防守 }; class Forwads :public Player { public: explicit Forwads(std::string name) :Player(name) {} void Attack()override { std::cout << "前锋:" << m_name << "进攻!" << std::endl; } void Defense()override { std::cout << "前锋:" << m_name << "防守!" << std::endl; } }; class Guards :public Player { public: explicit Guards(std::string name) :Player(name) {} void Attack()override { std::cout << "后卫:" << m_name << "进攻!" << std::endl; } void Defense()override { std::cout << "后卫:" << m_name << "防守!" << std::endl; } }; class ForeignCenter // Adaptee,此处为外籍中锋,它的接口和Target的接口不一样,是现在需要适配的接口 { private: std::string m_name; public: void setName(std::string name) { m_name = name; } void YaoMingAttack() { std::cout << "外籍中锋:" << m_name << "进攻!" << std::endl; } void YaoMingDefense() { std::cout << "外籍中锋:" << m_name << "防守!" << std::endl; } }; class Translator : public Player // Adapter , 通过在内部包装一个 Adaptee 对象,把 Adaptee 转换成 Target { private: ForeignCenter myForeignCenter; public: explicit Translator(std::string name) :Player(name) { myForeignCenter.setName(name); } void Attack()override { myForeignCenter.YaoMingAttack(); } void Defense()override { myForeignCenter.YaoMingDefense(); } }; int main() { Player *b = new Forwads("巴蒂尔"); b->Attack(); Player *m = new Guards("麦克格雷迪"); m->Attack(); Player *ym = new Translator("姚明"); ym->Attack(); ym->Defense(); delete b; delete m; delete ym; b = nullptr; m = nullptr; ym = nullptr; system("pause"); return 0; }四:适配器模式的优缺点 优点 (1)更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
(2)透明、简单
客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 ,也更直接
(3)更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
(4)解耦性
通过引入适配器,可以复用现有的类,而不需要修改源代码, 将目标类和被适配者解耦, 解决了接口和复用环境不一致的情况
(5)符合开放-关闭原则
当添加一个实现Adaptee接口的新类时,不必修改Adapter,Adapter就能对这个新类的实例进行适配。
(6)灵活性非常好
某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。
缺点 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
五:使用场景
一个程序想使用已经存在的类,但该类所实现的接口和当前程序所使用的接口不一致时,就应该考虑用适配器模式。两个类的职责相同或相似,但是具有不同的接口时要使用它;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大联系的一些类,包括一些可能在将来引进的类一起工作;双方都不太容易修改:第三方组件组件的接口,与系统接口不符注意:要在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同是就使用它。
