目录
1. 模式概述
1.1 定义
Adapter Design Pattern,用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
HeadFirst中形象的表示如下图:
通过上图可以极易理解其作用,那么如果给我们通过Java的一些特性自己实现其实也是不难。
1.2 应用场景
- 兼容老版本接口
- 统一多个类的访问方式
- 替换依赖的外部系统
- 封装有缺陷的接口设计
待补充
2. 模式的几种实现方式
2.1 类适配器: 基于继承
- 说明:Target目标类可以是接口、抽象类、实体类。
示例代码:
- 1. ITarget类:ITarget 表示要转化成的接口定义。
public interface ITarget {
void f1();
void f2();
void f3();
}
- 2. Adaptee原始类,是一组不兼容 ITarget 目标接口定义的类
public class Adaptee {
public void fa() {}
public void fb() {}
public void fc() {}
}
- 3. Adapter适配器类,将 Adaptee 转化成符合 ITarget 接口定义的类
public class Adapter extends Adaptee implements ITarget {
@Override
public void f1() {
super.fa();
}
@Override
public void f2() {
super.fb();
}
@Override
public void f3() {
// 可根据需要进行重写该方法
}
}
- 4. 客户端Client
public class Client {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.f1();
adapter.f2();
adapter.f3();
adapter.fa(); // 类适配器与对象适配器的最大区别在于其对于原始类的默认方法的直接引用
}
}
- 说明:由于类适配器实现的适配器模式,其适配器类继承了原始类,那么该适配器由于继承的特性便可直接引用原始类Adaptee的方法,这也是其于对象适配器的最大区别之处。
2.2 对象适配器:基于委托(也称为基于组合)
- 注:对象适配器ITarget 、Adaptee 类与类适配器一样,因此省去了
基于委托方式实现的适配器示例代码:
public class Adapter implements ITarget {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void f1() {
adaptee.fa();
}
@Override
public void f2() {
adaptee.fb();
}
@Override
public void f3() {
// 可根据目标接口进行重写该方法
}
}
类适配器与对象适配器的区别
- 如果Adaptee接口比较少,其实用两种方法都可以;
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口相近,推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,推荐使用对象适配器,因为组合结构相对于继承更加灵活。
3. 将枚举适配到迭代器
在做版本升级的时候,对于一些要废弃的接口,一般不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。
类似的场景如Java中对于Enumeration 到 Iterator 的替换。如果在对老版本进行升级过程中直接将旧版本替换掉,那么那些引用了旧版本代码的地方将要全部进行修改方可,这么做显然是改动很大,特别是对于一些很古老的系统(其内部可能基于旧版本运行也相当稳定并且已经很久没有升级或新功能引入)。
JDK1.0 中包含一个遍历集合容器的类 Enumeration。JDK2.0 对这个类进行了重构,将它改名为 Iterator 类,并且对它的代码实现做了优化。
- 1. JDK之Enumeration源码
// JDK1.0
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
- 2. JDK之Iterator源码
// JDK1.2,核心代码
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
观察上述两个类的代码,发现其实这两个类还是蛮相似,因此可将Enumeration统一适配成迭代器方式。但是需要注意的是,枚举类Enumeration是一个“只读”接口,因此没有对应的remove方法。因此在将枚举类组合到迭代器适配类中是无法实现一个有实际功能的remove方法,最多只能抛出一个运行时异常。幸运的是Iterator类默认实现一个remove方法并抛出UnsupportedOperationException异常。
- 3. 适配枚举类EnumerationIterator(详细源码可见JDK)
public class EnumerationIterator implements Iterator<Object> {
Enumeration<?> enumeration;
public EnumerationIterator(Enumeration<?> enumeration) {
this.enumeration = enumeration;
}
public boolean hasNext() {
return enumeration.hasMoreElements();
}
public Object next() {
return enumeration.nextElement();
}
public void remove() {
// 可根据自己的需要进行重写该方法
throw new UnsupportedOperationException();
}
}
- 4. 测试类EnumerationIteratorTestDrive
public class EnumerationIteratorTestDrive {
public static void main (String args[]) {
Vector<String> v = new Vector<String>(Arrays.asList(args));
Iterator<?> iterator = new EnumerationIterator(v.elements());
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
iterator .remove();// 抛出UnsupportedOperationException异常
}
}
4. 几个结构化模式的对比
- 装饰器模式:不改变原始类接口的情况下,对原始类功能进行增强,支持多个装饰器的嵌套使用。
- 代理模式:不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
- 桥接模式:目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
- 适配器模式:是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
- 外观模式:将原系统中复杂的访问方式进行封装,从而提供一个更加简化的访问方式,让原系统的操作更简单。
- 参考文档
极客时间《设计模式之美》、HeadFirst《设计模式》