8、设计模式-结构型模式-适配器模式
适配器模式
在软件开发中,有时也存在类似这种不兼容的情况
引入一个称之为适配器的角色来协调这些存在不兼容的结构
这种设计方案即为适配器模式
在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装
的对象称为适配者(Adaptee),即被适配的类。
适配器的实现就是把客户类的请求转化为对适
配者的相应接口的调用。
当客户类调用适配器的方法时,在适配器类的内部将调
用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,
适配器让那些由于接口不兼容而不能交互的类可以一起工作。
定义:
将一个接口转换成客户希望的另一个接口,使接口不兼容的那
些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可
以作为对象结构型模式。
注意:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。
在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有
任何关系的类可以协同工作。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种
对象适配器模式中,适配器与适配者之间是关联关系
类适配器模式中,适配器与适配者之间是继承(或实现)关系
角色:
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以
是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进
行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个
Adaptee对象使二者产生联系。
Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口
需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可
能没有适配者类的源代码。
事例:
//目标接口 interface ScoreOperation { public int [] sort(int array[]);//成绩排序 public int search(int array[],int key);//成绩查找 }
//适配者:快速排序 public class QuickSort { public int[] quickSort(int array[]) { sort(array,0,array.length-1); return array; } public 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; } public void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } }
//适配者:二分查找 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; //找到元素返回1 } } return -1; //未找到元素返回-1 } }
//适配器 public class OperationAdapter implements ScoreOperation { private QuickSort sortObj; //定义适配者QuickSort对象 private BinarySearch searchObj; //定义适配者BinarySearch对象 public OperationAdapter() { sortObj = new QuickSort(); searchObj = new BinarySearch(); } @Override public int[] sort(int[] array) { return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法 } @Override public int search(int[] array, int key) { return searchObj.binarySearch(array,key); //调用适配者类BinarySearch的查找 } }
public class client { public static void main(String[] args) { ScoreOperation operation; //针对抽象目标接口编程 operation = new OperationAdapter(); int scores[] = {84,76,50,69,90,91,88,96}; //定义成绩数组 int result[]; int score; System.out.println("排序结果:"); result = operation.sort(scores); //遍历 for (int i : scores){ System.out.print(i + ","); } System.out.println(); System.out.println("查找成绩等于90的学生"); score = operation.search(result,90); if ( score != -1){ System.out.println("找到成绩为90的学生"); }else { System.out.println("没有成绩为90的学生"); } System.out.println("查找成绩0:"); score = operation.search(result,0); if (score != -1) { System.out.println("找到成绩0。"); } else { System.out.println("没有找到成绩0。"); } } }
此时的结构:
如果需要使用其他排序算法类和查找算法类,可以增加一个新的适配器类,使用新的
适配器来适配新的算法,原有代码无须修改。
可以在不修改客户端代码的情况下使用新的适配器,无须修改源代码,符合“开闭原则”。
类适配器(很少使用)
类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同
对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系
适配器类实现了抽象目标类接口Target,并继承了适配者类,在适
配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。
class Adapter extends Adaptee implements Target { public void request() { specificRequest(); } }
双向适配器(使用较少)
如果在适配器中同时包含对目标类和适配者类的引用,适配者
可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配
器就是一个双向适配器
class Adapter implements Target,Adaptee { //同时维持对抽象目标类和适配者的引用 private Target target; private Adaptee adaptee;
public Adapter(Target target) { this.target = target; }
public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
public void request() { adaptee.specificRequest(); }
public void specificRequest() { target.request(); } }
优点:
(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而
言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3) 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修
改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
类适配器模式:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配
器的灵活性更强
对象适配器模式:
(1) 一个对象适配器可以把多个不同的适配者适配到同一个目标;
(2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原
则”,适配者的子类也可通过该适配器进行适配。
缺点:
类适配器模式
(1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适
配多个适配者;
(2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
(3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一
定的局限性。
对象适配器模式
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉
适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然
后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂
适用场景:
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有
这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可
能在将来引进的类一起工作。