引言
由于无法直接使用某个类中的方法而采取的一种中间类转换的策略。将一个类的接口转换成另一个接口,让原本接口不兼容的类可以兼容。
适配器模式可以分为三种:类适配器、对象适配器、接口适配器。它们之间的区别主要体现在适配器角色与被适配角色之间的依赖关系上。如类适配器是通过继承的方式,令适配器继承被适配类。
我们可以将适配器理解为两个不兼容的接口之间的桥梁。这是一种结构型模式。
虽然解决了老接口与新代码之间的兼容问题,但是适配器模式也存在不容忽视的缺点。过多的使用适配器会让系统变得凌乱,不易整体把握,因此,如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
注意,适配器不是在详细设计时添加的,而是解决正在服役的项目的兼容问题时添加的,而且不可以大量使用。
一、类适配器
首先描述一下具体场景。
以电压适配为例。假设目前有一个用于输出电压的接口 Voltage,提供一个 output() 输出电压方法,且有一个可以输出220V标准家庭电压的实现类 Voltage220V。目前的系统中有一些家用电器的类,如TV(电视机类)、Fridge(电冰箱类)等,它们有一个charge() 方法,需要依赖 Voltage.output() 提供的电压为其充电。
Voltage220V的output()方法实现了 220V电压的输出功能,所以一切安好,但是今天我们买了一部手机,需要 5V的电压为其充电,但是目前系统中只有220V电压的输出,这时我们该如何解决呢?如下的类图所示:
此时我们最常想到的是再新建一个 Voltage 的实现类,可以叫 Voltage5V,然后实现其output() 方法。如下所示:
这种方法是一种解决思路,这并没有什么问题,实际上,在真正的生产开发中,也有很多是以这种方式解决的。当然,如果真的能够以这种方式解决问题是最好的。
但是,如果系统中 220V 电压是一个标准电压,我们无法新加一个与之等价的类,或者必须要基于已有的实现来解决的话,那么适配器模式就派上了用场,这也是适配器模式的一个主要特点,就是对已有方法的复用。于是,就有了下面这种适配器模式的类图:
代码实现如下:
/*** 电压接口*/
public interface Voltage {/** 电压输出*/int output();
}
/*** 220V电压实现*/
public class Voltage220V implements Voltage {private static final int voltage = 220;@Overridepublic int output() {System.out.println("输出" + voltage + "电压");return voltage;}
}
/*** 220V电压适配器*/
public class Voltage220VAdapter extends Voltage220V implements Voltage {@Overridepublic int output() {int superVoltage = super.output();System.out.println("父类输出电压:" + superVoltage + "V");int adaptedVoltage = superVoltage / 44;System.out.println("已将" + superVoltage + "V电压为" + adaptedVoltage + "V。");return adaptedVoltage;}
}
/*** 手机类*/
public class Phone {/*** 充电需要电压:5V*/public void charge(Voltage voltage) {if (voltage.output() == 5) {System.out.println("电压为5V,正在充电~");} else {System.out.println("电压异常,手机未充电!");}}
}
以上代码是适配器中几个关键角色,其中Voltage220V代表被适配类,Voltage220VAdapter 就代表220V电压的适配器类。以下是测试代码:
public class Test {public static void main(String[] args) {Phone phone = new Phone();phone.charge(new Voltage220VAdapter());}
}
// 输出:
输出220电压
父类输出电压:220V
已将220V电压为5V。
电压为5V,正在充电~
二、对象适配器
对象适配器与类适配器的区别是与被适配器类的关系,由泛化关系(继承)变为依赖关系(组合或聚合)。如下图所示:
可以看到,适配器类Voltage220VAdapter以组合的方式将Voltage220V的一个对象引用到其内部,这在一定程度上避免了因为继承关系带来的较强的耦合度。同时,该适配器类可以更加灵活,去适配更多的电压,如1000V等等。代码如下:
/*** 220V电压适配器*/
public class Voltage220VAdapter implements Voltage {private Voltage220V v220;public Voltage220VAdapter(Voltage220V v220) {this.v220 = v220;}@Overridepublic int output() {int srcVoltage = v220.output();System.out.println("原输出电压:" + srcVoltage + "V");int adaptedVoltage = srcVoltage / 44;System.out.println("已将" + srcVoltage + "V电压为" + adaptedVoltage + "V。");return adaptedVoltage;}
}
对象适配器与类适配器唯一的不同仅仅是对被适配类的依赖关系,由继承变为了组合或聚合,因此只有适配器本身稍有变化,去掉了继承关系,增加了一个 v220 字段。其他的接口都没什么变化。在测试代码中,我们需要为适配器类的构造器传入一个被适配的对象,如下所示:
public class Test {public static void main(String[] args) {Phone phone = new Phone();phone.charge(new Voltage220VAdapter(new Voltage220V()));}
}
// 输出:
输出220电压
原输出电压:220V
已将220V电压为5V。
电压为5V,正在充电~
三、接口适配器
接口适配器模式,也叫缺省适配器模式。
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认的空方法实现,这样,该抽象类的子类就可以有选择地覆盖父类的某些方法来实现需求。
接口适配器其实挺好理解的,在很多类库框架中也会用到,经常会看到一些方法仅仅是一个空方法,其实就用到这种接口适配器模式。相比于类适配器或对象适配器,这种接口适配器并不是为了实现某种兼容采用的解决办法,而更多的是为了保证各个子类的功能保持“单一职责原则”,避免将接口中不需要的方法暴露给外部调用者。