一、适配器模式概述
在软件设计过程中,有时现在有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现在有类中方法名与目标类中定义的方法名不一致等原因造成的。那么如何在不修改原有目标类和客户端代码的基础上确保能够使用现有类中的方法能被目标类使用呢?这就是适配器能够解决的问题。适配器模式可以将一个接口转换成客户希望的另一个接口,它使接口不兼容的那些类可以一起工作。
适配器模式可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配者(Adaptee),即被适配的类,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。当客户类调用适配器的方法时,适配器类的内部将调用适配者类的方法,此过程对客户类是透明的,客户类不能直接访问适配者类。
二、 适配器模式结构分析
1. 首先让我们来看一下类适配器模式和对象适配器模式的结构图及简要分析
2. 从上面的两个结构图中可以看出,适配器模式包含如下角色:
(1)Target(目标抽象类)
目标抽象类定义客户需要的接口,可以由抽象类或接口来定义,当然也可以是具体类。
(2)Adapter(适配器类)
适配器类是适配器模式的核心,它可调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。从上面的两结构图可以看到:在类适配器中,Adapter通过实现Target接口和继承Adaptee类来为它们适配;而在对象适配器中,则通过继承Target并关联一个Adaptee对象来适配。
(3)Adaptee(适配者类)
适配者是被适配的角色,它定义了一个已经存在的接口,即它一般是一个具体类,包含了客户希望使用的方法。
(4)Client(客户类)
在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
3. 下面再让我们简要分析一下这两种适配器模式的异同
(1)类适配器
从类适配器模式的结构图中可以看到:一方面,适配者类Adaptee没有request()方法,而客户类需要这个方法;另一方面,适配者类中实现了specificRequest()方法,该方法所提供的实现是客户所需要的。为了使客户能够使用适配者类,我们提供了Adapter类作为一个中间类,Adapter类实现了目标接口,并继承适配者类,然后在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,从而达到适配的功能。其典型实现代码如下:
public class Adapter1 extends Adaptee implements Target{
public void request()
{
specificRequest();
}
}
(2)对象适配器
从上面的两个结构图可以看到,对象适配器模式和类适配器模式在结构上并没有太大的区别。同样的,在对象适配器模式中,客户需要调用request()方法,而适配者Adaptee没有该方法,但是它所提供的specificRequest()方法是客户端需要的。所以我们用一个Adapter类作为适配,在这个类中定义一个适配者类的实例,这样就可以将客户端和适配者关联起来,在适配器的request()方法中调用适配者的specificRequest()方法。其典型代码如下:
public class Adapter2 extends Target{
private Adaptee adaptee;
public Adapter2(Adaptee adaptee)
{
this.adaptee = adaptee;
}
public void request()
{
adaptee.specificRequest();
}
}
二、 适配器模式实例
1、智能游戏角色的创建:
假设我们要设计一些游戏角色,它们能通过它们的学习能力来学习其它角色的技能。如当前角色AILion是进入系统的一个新角色,它什么技能都还没有,而AIHong擅长游泳和飞行。当这两者在游戏场景相遇时,AILion会发现AIHong的技能,它很想掌握这些技能,怎么办呢?我们可以定义一个适配器类Adapter,让它实现AILion接口,并继承AIHong类。然后让AILion去引用Adapter,这样一来,AILion就可以拥有AIHong的技能了。下面是这个描述的类图:
下面是这个例子的实例代码
(1)目标抽象类,客户端针对这个类进行编程,此类中声明了客户端所调用的方法,这两个方法的具体实现是还没有定义的。
public interface AILion {
public void skill_1();
public void skill_2();
}
(2)适配者类,这个类包含了具体的方法,这些方法的功能正是用户所需要的(即此类包含了客户希望AILion角色能拥有的技能)。
public class AIHong {
public void swimming()
{
System.out.println("I'm good at swimming");
}
public void fly()
{
System.out.println("I can fly");
}
}
(3)适配器类,由于AILion没有AIHong的技能,但是客户希望它有,所以在些通过继承AIHong并实现AILion接口的方式来实现在AILion的未定义方法中来调用AIHong的方法(即调用其技能)。根据里氏代换原则,这里的Adapter可以是别的类型的Adapter,那这个Adapter可以继承不同的类,只要它实现AILion接口就行了。
public class Adapter extends AIHong implements AILion{
public void skill_1()
{
System.out.println("I learn this skill from AIHong");
super.swimming();
}
public void skill_2()
{
System.out.println("I learn this skill from AIHong");
super.fly();
}
}
(4)客户端测试类Client
public class Client {
public static void main(String args[])
{
AILion lion = new Adapter();
lion.skill_1();
lion.skill_2();
}
}
2. 一个简单的对象适配器模式实例,经过上面的分析,相信大家已经清楚各角色的意义了,这个例子我就不扯太多了,先来看一下类图
其中要注意的一点是:Adapter类中定义了一个Adaptee类型的对象adaptee,这就是对象适配器的一个关键特征了。实例代码如下
#include <iostream>
using namespace std;
class Target
{
public:
virtual void request()
{// Undefine operation
}
};
class Adaptee
{
public:
void specificRequest()
{
cout << "specificRequest can defined at here" << endl;
}
};
class Adapter:public Target
{
private:
Adaptee *adaptee;
public:
virtual void request() // 覆盖父类的函数
{
adaptee->specificRequest();
}
Adapter()
{
adaptee = new Adaptee();
}
~Adapter()
{
delete adaptee;
}
};
int main()
{
Target *target = new Adapter();
target->request();
delete target;
return 0;
}
三、总结
适配器模式将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,不用修改原有代码。这样不仅能提高适配者的可重用性,而且还可以通过配置文件方便地更换适配器类,也可以在适配器类中更换(覆盖)一些适配者类的方法,使适配器更加灵活。由此可见适配器模式的灵活性和可扩展性都非常好。
另一方面,由于Java不支持多重继承,所以一次最多能适配一个适配类,而且其目标抽象类必须是接口,所以其使用有一定的局限性。
总结到此,实际应用中可根据需要选择使用类适配器或者对象适配器模式…