意图
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。换个包装再利用。
适用性
以下情况使用Adapter模式:
1. 你想使用一个已经存在的类,而它的接口不符合你的要求
2. 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
3. (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
4. 想使用一个已经存在的类,但如果它的接口和你的要求不相同时。可以使用适配器模式。两个类所做的事情相同或相似,但接口不同时可以使用。
结构
参与者
Target
定义Client使用的与特定领域相关的接口。
Client
与符合Target接口的对象协同
Adaptee
定义一个已经存在的接口,这个接口需要适配
Adapter
对Adaptee的接口与Target接口进行适配
代码
public class Adaptee {
public void oldMethod(){
System.out.println("===Adaptee===oldMethod====");
}
}
public interface Target {
public void newMethod();
}
public class Adapter implements Target{
private Adaptee adaptee;
public Adapter(Adaptee adaptee){this.adaptee = adaptee;}
public void newMethod() {
adaptee.oldMethod();
System.out.println("===Adapter===newMethod====");
}
}
public class Client {
public static void main(String[] args) {
Target target = new Adapter(new Adaptee());
target.newMethod();
}
}
协作
Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求。
类对象适配器对比
类适配器
- 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类似以及所有它的子类时,类Adapter将不能胜任工作。
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类
- 仅仅引入一个对象,并不需要额外的指针以简洁得到adaptee
对象适配器
- 允许一个Adapter与多个Adptee,即Adaptee本身以及它的所有子类(如果有子类的话),同时工作。Adapter也可以一次给所有的Adaptee添加功能。
- 使得重定义Adaptee的行为比较困难。这就需要生产Adaptee的子类并且使用Adapter引用这个子类而不是引用Adaptee本身。
效果
Adapter的匹配程度
对于Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名)到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度。
可插入的Adapter(内部接口适配器)
当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。
使用双向适配器提供透明操作
使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容Adaptee的接口,因此并不是所有的Adaptee对象可以被使用的地方都可以被使用。双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。
优点
- 将目标类和适配者类解耦
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性
- 灵活性和扩展性都非常好,符合开闭原则
缺点
一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。
实现
使用C++实现适配器
在使用C++实现适配器类时,Adapter类应该采用公共方法继承Target,并且用私有方式继承Adaptee类。因此,Adapter类应该是Target的子类型,但不是Adaptee的子类型。
可插入的适配器
窄接口
首先(这也是所有者三种实现都要做的)是为Adaptee找到一个“窄”接口,即可用于适配最小操作集。因为包含较少操作的窄接口相对包含校对操作的宽接口比较容易进行匹配。最小接口集仅包含两个操作:一个操作定义如何在层次接口中表示一个节点,另一个操作返回该节点的子节点。
实现方式:
1. 使用抽象操作:在TreeDisplay中定义窄Adaptee接口相应的抽象操作。这样就由子类来实现这些抽象操作并匹配具体的树形结构的对象。
使用代理对象:这种方法中,TreeDisplay将访问树结构的请求转发到代理对象。TreeDisplay的客户进行一些选择,并将这些选择提供给代理对象,这样客户就可以对适配加以控制
参数化的适配器:通常在Smalltalk中支持可插入适配器的方法是,用一个或多个模块对适配器进行参数化。模块构造支持无子类的适配。一个模块可以匹配一个请求,并且适配器可以为每个请求存储一个模块。在本例中意味着,TreeDisplay存储的一个模块用来将一个节点转化成一个GraphicNode,另外一个模块用来存取一个节点的子节点。如果你在一个类中创建接口适配,这种方法提供了另外一种选择,它相对于子类化方法来说更方便一些。
经典例子
电源适配器
Java IO
相关模式
Bridge Pattern
Bridge模式的结构与对象适配器类似,但是Bridge模式的出发点不同:Bridge目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而Adapter则意味着改变一个已有对象的接口。Adapter是用来连接相异接口,而Bridge是用来连接功能阶段和实现阶段
Decorator Pattern
Decorator模式增强了其他对象的功能而同时不改变它的接口。因此decorator对应用程序的透明性比适配器更好。结果是decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。Adapter是填补接口之间的差距,Decorator是不需要修改接口即可新增功能的Pattern。
Proxy Pattern
Proxy模式在不改变它的接口的条件下,为另一个对象定义一个代理。
敬请期待“门面模式(Facade 外观模式,对象结构型模式)”