适配器模式(Adapter Pattern)
用来消除不兼容性。我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容(重要点在于,电脑工作电压和民用电压都不能被我们修改,所以我们引入了一个适配器,在不修改电脑工作电压和民用电压的情况下,使得电脑获得了20V 输入电压,这就是适配器模式的关键,即被适配的两方不能被修改)。在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。
在以下情况下可以考虑使用适配器模式:
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
class Adapter : public Target // 继承Target类
{
private :
Adaptee adaptee; //维持一个对适配者对象的引用 ,关联Adaptee类
public:
Adapter(Adaptee adaptee) {
this->adaptee = adaptee;
}
void request() {
adaptee.specificRequest(); //转发调用
}
};
#include
using namespace std;
// 抽象类
class Target
{
public:
virtual ~Target() {};
// 目标方法
virtual void request() = 0;
};
class Adaptee
{
public:
// 被适配的方法
void specificRequest()
{
cout << "specificRequest()" << endl;
}
};
class Adapter : public Target
{
public:
Adapter()
{
p = Adaptee();
}
// 目标方法
void request()
{
// 调用被适配的方法
p.specificRequest();
}
private:
Adaptee p;
};
int main()
{
Target& t = Adapter();
t.request();
}
using System;
using System.Collections.Generic;
class Program
{
interface Target
{
// 目标接口
void request();
}
class Adaptee
{
// 被适配接口
public void specificRequest()
{
Console.WriteLine("specificRequest()");
}
}
class Adapter : Target
{
private Adaptee ad = new Adaptee(); // 关联到Adaptee
public void request()
{
ad.specificRequest();
}
}
public static void Main()
{
Target t = new Adapter();
t.request();
}
}
结果:
类适配器和对象适配器的组成元素一样,唯一的区别是Adapter针对Adaptee,从关联关系变成了继承关系,并把Target写成了接口(因为C#中类只能单继承)。
#include
using namespace std;
// 抽象类
class Target
{
public:
virtual ~Target() {};
// 目标方法
virtual void request() = 0;
};
class Adaptee
{
public:
// 被适配的方法
void specificRequest()
{
cout << "specificRequest()" << endl;
}
};
class Adapter : public Target, public Adaptee // C++实现中只在Adapter类和对象适配器有区别
{
public:
Adapter()
{
}
// 目标方法
void request()
{
// 调用被适配的方法
specificRequest();
}
};
int main()
{
Target& t = Adapter();
t.request();
}
C#
using System;
using System.Collections.Generic;
class Program
{
interface Target
{
// 目标接口
void request();
}
class Adaptee
{
// 被适配接口
public void specificRequest()
{
Console.WriteLine("specificRequest()");
}
}
class Adapter : Adaptee, Target
{
public void request()
{
specificRequest();
}
}
public static void Main()
{
Target t = new Adapter();
t.request();
}
}
无论是对象适配器模式还是类适配器模式都具有如下优点:
(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3) 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体来说,类适配器模式还有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还有如下优点:
(1) 一个对象适配器可以把多个不同的适配者适配到同一个目标;
(2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。
类适配器模式的缺点如下:
(1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
(2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
(3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
对象适配器模式的缺点如下:
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。