适配器模式
1.适配器模式动机及定义
1.1模式动机
在现实生活中,经常存在一些不兼容的事物。如某电器的工作电压与家庭交流电电压不一致,网络速度与计算机处理速度不一致,某硬件设备提供的接口与计算机支持的接口不一致等。在这种情况下,我们可以通过一个新的设备使原本不兼容的事物可以一起工作,这个新的设备称为适配器。在软件开发中,也存在一些不一致的情况,同样,也可以通过一种称为适配器模式的设计模式来解决这类问题。
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者(Adaptee),即被适配的类。适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说,当客户类调用适配器对方法时,在适配器类对内部将调用适配者类对方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使用由于接口不兼容而不能交互的类可以一起工作,这就是适配器模式的模式动机。
1.2模式定义
适配器模式定义(Adapter Pattern)定义:将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构性模式。
英文定义:“Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”
2.适配器模式结构与分析
适配器模式包括类适配器和对象适配器,下面分别对两种适配器进行结构分析。
2.1模式结构
图1 类适配器模式结构图
当使用继承时,接口有污染,不符合迪米特原则,会将父类接口方法也实现。
图2 对象适配器模式结构图
适配器模式包含如下角色。
1.Target(目标抽象类)
目标抽象类定义客户要用的特定领域的接口,可以是个抽象类或接口,也可以是具体类;在类适配器中,由于Java语句不支持多重继承,它只能是接口。(但在其他语言中,可以是一个类,例如:C++)
2.Adapter(适配器类)
适配器类可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。适配器Adapter是适配器模式的核心,在类适配器中,它通过实现Target接口并继承Adaptee类来使二者产生联系;在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
3.Adaptee(适配者类)
适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配。适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。
4.Client(客户类)
在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
3.适配器模式实例与解析
题目:现有一个接口DataOperation定义了排序方法sort(int[]) 和查找方法search(int[], int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法,类BinarySearch 的binarySearch(int[], int)方法实现了二分查找算法。现使用适配器模式设计一个系统,在不修改源代码的情况下将类QuickSort和类BinarySearch的方法适配到DataOperation接口中。绘制类图并编程实现。 (要求实现快速排序和二分查找)。
3.1实例类图
3.2实例代码及解释
1.目标抽象类Target(DataOperation)
package adapter_pattern;
/*
* Target(目标抽象接口):DataOperation
* */
public interface DataOperation {
public int[] sort(int array[]);
public int search(int array[],int key);
}
DataOperation充当目标抽象角色,客户端针对抽象的DataOperation接口进行编程,在DataOperation中声明了客户端所调用的业务方法,sort()方法和search()方法。
2.适配者类Adaptee、(QuickSort)
package adapter_pattern;
/*
* Adaptee(适配者类):QuickSort
* */
public class QuickSort {
public int[] quickSort(int array[]){
sort(array,0,array.length-1);
return array;
}
private void sort(int[] array,int p,int r){
int q = 0;
if (p < r){
q = partition(array,p,r);
sort(array,p,q-1);
sort(array,q+1,r);
}
}
public int partition(int[] a,int p,int r){
int x = a[r];
int j = p-1;
for (int i = p;i <= r-1;i++){
if (a[i] <= x){
j++;
swap(a,j,i);
}
}
swap(a,j+1,r);
return j+1;
}
private void swap(int[] a,int j ,int i){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
QuickSort类是一个已存在的具体类,它包含用户所需业务方法的具体实现,如本类中的quickSort(),partition()和swap()方法,但是方法名等与Target接口不一致,甚至没有该类的源代码。
3.适配者类Adaptee(BinarySearch)
package adapter_pattern;
/*
* Adaptee(适配者类):BinarySearch
* */
public class BinarySearch {
public int binarySearch(int array[],int key){
int low = 0;
int high = array.length-1;
while(low <= high){
int mid = (low+high)/2;
int midVal = array[mid];
if (midVal<key)
low = mid + 1;
else if (midVal>key)
high = mid - 1;
else
return 1;
}
return -1;
}
}
BinarySearch也是一个适配者类,其中也有一个binarySearch()方法。
4.适配器类Adapter(Adapter)
package adapter_pattern;
/*
* Adapter(适配器类):Adapter
* */
public class Adapter implements DataOperation {
private QuickSort sortObj;
private BinarySearch searchObj;
public Adapter(QuickSort sortObj, BinarySearch searchObj) {
this.sortObj = sortObj;
this.searchObj = searchObj;
}
@Override
public int[] sort(int[] array) {
return sortObj.quickSort(array);
}
@Override
public int search(int[] array, int key) {
return searchObj.binarySearch(array,key);
}
}
Adapter类是适配器模式的核心类,在此处使用的是对象适配器,即Adapter与QuickSort、BinarySearch之间是关联关系而不是继承关系。需要其他排序算法类和查找算法类时可以增加新的适配器类;
3.3辅助代码
客户端测试类Client如下:
package adapter_pattern;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
DataOperation operation;
QuickSort sortObj = new QuickSort();
BinarySearch searchObj = new BinarySearch();
operation = new Adapter(sortObj,searchObj);
Random random = new Random();
int array[] = random.ints(10,0,100).toArray();
int[] result;
int value;
System.out.println("生成随机数组如下:");
System.out.println(Arrays.toString(array));
System.out.println("快速排序结果如下:");
result = operation.sort(array);
for (int i : array){
System.out.print(i+" ");
}
System.out.println();
System.out.println("请输入需要查询的关键字:");
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
value = operation.search(result,i);
if (value !=-1){
System.out.println("找到该关键字");
}else {
System.out.println("无法找到该关键字,请检查后重新输入!");
}
}
}
3.4结果
4.适配器模式效果与应用
4.1 适配器模式的优点
无论是类适配器模式还是对象适配器模式,都具有如下优点。
(1)将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
(2)增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
(3)灵活性和扩展性都非常好,可以通过使用配置文件,可以方便地更换适配器,也可以不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体的说,类适配器模式的优点还有:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器的优点:对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
4.2适配器模式的缺点
(1)类适配器模式的缺点:对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
(2)对象适配器模式的缺点:与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂。
4.3模式适用环境
在以下情况可以使用适配器模式。
- 系统需要使用现有的类,而这些类的接口不符合(不兼容)系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。。