第七章 适配器模式 - 不兼容结构的协调
一、概念
如笔记本的电源接口只支持20V电压,而家庭用电的电压为220V,肯定无法直接通过一根电线直接为笔记本提供电源。此时,就需要一个变压器电源,来将220V电压转换为20V电压。这个变压器就是适配器模式的一种。
适配器模式:将一个类的接口与另一个类的接口匹配利用,而无须修改原来的适配者,解耦合抽象目标类接口。
适配器模式可以是类结构型模式,也可以是对象结构型模式。
1.2 适配器模式中的几个角色
- Target - 目标抽象类
- 目标抽象类定义客户所需接口
- 抽象类、接口或具体类
- 类似上面的笔记本
- Adapter - 适配器类
- 适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配
- Adaptee - 适配者类
- 被适配的角色(如220V的家庭电源,我们需要将其转换为20V电压输出)
- 它定义了一个已经存在的接口,这个接口需要适配
- 适配者类一般是个具体类,包含了客户希望使用的业务方法
1.3 对象适配器 - 实例
// 抽象成绩操作类 - Target目标接口
interface ScoreOperation {
public int[] sort(int array[]); // 成绩排序
public int search(int array[],int key); // 成绩查找
}
// 快速排序类 - Adaptee 适配者类
class QuickSort{
public int[] quickSort(int array[]){
// 自己实现
}
}
// 二分查找类 - Adaptee 适配者类
class BinarySearch{
public int binarySearch(int array[],intkey){
// 自己实现
}
}
// 操作适配器 - Adapter 适配器
class OperationAdapter implements ScoreOperation{
private QuickSort sort = new QuickSort;
private BinarySearch search = new BinarySearch();
// 调用适配者类QuickSort的排序方法
public int[] sort(int array[]){
return sort.quickSort(array);
}
// 调用适配者类BinarySearch的查找方法
public int search(int array[],int key){
return search.binarySearch(array,key);
}
}
1.4 类适配器
类适配器与对象适配器的区别在于:
对象适配器与适配者之间的关系是关联关系
,而类适配器中的关系则是继承关系
。
如下:
class Adapter extends Adaptee implements Target{
public void request(){
specificRequest();
}
}
由于Java、C#等语言不支持多重继承,因此类适配器在使用时受到诸多限制。如Target必须是接口,Adapter不能是Final类等等。
1.5 双向适配器
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者和目标类可以通过它调用彼此的方法,那么该适配器就是一个双向适配器。如下:
class Adapter implements Adaptee,Target{
private Target target;
private Adaptee adaptee;
public Adapter(Target target){
this.target = target;
}
public Adapter(Adaptee adpatee){
this.adaptee = adaptee;
}
public void request(){
adaptee.specificRequest();
}
public void specificRequest(){
target.request();
}
}
1.6 缺省适配器
缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
二、总结
优点
- 目标类与适配者类解耦,通过引入一个适配器类来重用现有的适配者类
- 增加类的透明性和复用性,具体业务过程封装在适配者类中,对客户端透明,提供了适配者的复用性,同一个适配者类可以在多个不同系统中复用
- 灵活性和扩展性好,可以通过配置文件的方式更换适配器,符合“开闭原则”
缺点
- Java、C#不支持多重继承,一次只能适配一个适配者类
- 适配者类不能为最终类
- Java、C#中,类适配器模式的目标抽象类必须是接口
适用场景
- 系统需要使用一些现有的类,但该类的接口不符合系统需要
- 创建一个可重复使用的类,如上面的快速排序,可以做成一个适配者,为其它适配器类所调用
三、练习
// SD卡接口及其具体实现
public interface SDCard {
String readSD(); // 读SD卡
int writeSD(String message); // 写SD卡
}
class SDCardImpl implements SDCard {
public String readSD() {
return "读取SD卡";
}
public int writeSD(String message) {
// 假装写入数据至SD卡中了
return 0;
}
}
// 电脑类,具有读取SD卡的功能
class Computer {
String readSD(SDCard sdCard) {
return sdCard.readSD();
}
}
// ----- 新版的TF卡上市,系统需要在不修改原有SD卡接口的情况下,支持新版的TF卡 -----
// TF卡接口及其实现
interface TFCard {
String readTF(); // 读TF卡
int writeTF(String message); // 写TF卡
}
class TFCardImpl implements TFCard {
public String readTF() {
return "读取TF卡";
}
public int writeTF(String message) {
return 0;
}
}
// TF卡的适配器,继承与目标接口,但是将SD卡接口适配为TF卡的接口
class TFCardAdapter implements SDCard {
private TFCard tfCard;
public TFCardAdapter(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public String readSD() {
return tfCard.readTF();
}
@Override
public int writeSD(String message) {
return tfCard.writeTF(message);
}
}
// 测试客户端
class Client {
public static void main(String[] args) {
SDCard sdCard = new SDCardImpl();
SDCard tfCard = new TFCardAdapter(new TFCardImpl()); // 通过适配器,将TFCard适配为SDCard
Computer computer = new Computer();
String s = computer.readSD(sdCard);
String s1 = computer.readSD(tfCard);
System.out.println(s);
System.out.println(s1);
}
}
适配器类的名称尽量做到见名知意,因为客户端表面上调用的是SDCard,但是内部却被适配成了TFCard,如果系统中存在大量这种代码,会使系统十分的混乱。
如果适配器大量存在的话,可以考虑重构代码,而不是继续适配下去。