完整解决方案
对象适配器:
Sunny软件公司开发人员决定使用适配器模式来重用算法库中的算法,其基本结构如图所示:
算法库重用结构图
在图中,ScoreOperation接口充当抽象目标,QuickSort和BinarySearch类充当适配者,OperationAdapter充当适配器。完整代码如下所示:
//抽象成绩操作类:目标接口 interface ScoreOperation { public int[] sort(int array[]); //成绩排序 public int search(int array[], int key); //成绩查找 }
//快速排序类:适配者 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; } }
//二分查找类:适配者 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 } }
//操作适配器:适配器 class OperationAdapter implements ScoreOperation { private QuickSort sortObj; //定义适配者QuickSort对象 private BinarySearch searchObj; //定义适配者BinarySearch对象 public OperationAdapter() { sortObj = new QuickSort(); searchObj = new BinarySearch(); } public int[] sort(int array[]) { return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法 } public int search(int array[], int key) { return searchObj.binarySearch(array, key); //调用适配者类BinarySearch的查找方法 } }
为了让系统具备良好的灵活性和可扩展性,我们引入了工具类XMLUtil和配置文件,其中,XMLUtil类的代码如下所示:
import javax.xml.parsers.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; class XMLUtil { //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象 public static Object getBean() { try { //创建文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("config.xml")); //获取包含类名的文本节点 NodeList nl = doc.getElementsByTagName("className"); Node classNode = nl.item(0).getFirstChild(); String cName = classNode.getNodeValue(); // 通过类名生成实例对象并将其返回 Class c = Class.forName(cName); Object obj = c.newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } }
配置文件config.xml中存储了适配器类的类名,代码如下所示:
<?xml version="1.0"?> <config> <className>OperationAdapter</className> </config>
编写如下客户端测试代码:
class Client { public static void main(String args[]) { ScoreOperation operation; //针对抽象目标接口编程 operation = (ScoreOperation) XMLUtil.getBean(); // 读取配置文件,反射生成对象 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("查找成绩92:"); score = operation.search(result, 92); if (score != -1) { System.out.println("找到成绩92。"); } else { System.out.println("没有找到成绩92。"); } } }
编译并运行程序,输出结果如下:
成绩排序结果: 50,69,76,84,88,90,91,96, 查找成绩90: 找到成绩90。 查找成绩92: 没有找到成绩92。
在本实例中使用了对象适配器模式,同时引入了配置文件,将适配器类的类名存储在配置文件中。如果需要使用其他排序算法类和查找算法类,可以增加一个新的适配器类,使用新的适配器来适配新的算法,原有代码无须修改。通过引入配置文件和反射机制,可以在不修改客户端代码的情况下使用新的适配器,无须修改源代码,符合“开闭原则”。
类适配器:
除了对象适配器模式之外,适配器模式还有一种形式,那就是类适配器模式,类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不同,对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系,类适配器模式结构如图所示:
图为适配器模式结构图
根据类适配器模式结构图,适配器类实现了抽象目标类接口Target,并继承了适配者类,在适配器类的request()方法中调用所继承的适配者类的specificRequest()方法,实现了适配。
典型的类适配器代码如下所示:
class Adapter extends Adaptee implements Target { public void request() { specificRequest(); } }
由于Java、C#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adapter为最终(Final)类,也无法使用类适配器。在Java等面向对象编程语言中,大部分情况下我们使用的是对象适配器,类适配器较少使用。
双向适配器 :
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器,其结构示意图如图所示:
上图双向适配器结构示意图:
双向适配器的实现较为复杂,其典型代码如下所示:
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(); } }
在实际开发中,我们很少使用双向适配器。
适配器模式总结
适配器模式将现有接口转化为客户类所期望的接口,实现了对现有类的复用,它是一种使用频率非常高的设计模式,在软件开发中得以广泛应用,在Spring等开源框架、驱动程序设计(如JDBC中的数据库驱动程序)中也使用了适配器模式。
无论是对象适配器模式还是类适配器模式都具有如下优点:
(1) 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
(2) 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
(3) 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体来说,类适配器模式还有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还有如下优点:
(1) 一个对象适配器可以把多个不同的适配者适配到同一个目标;
(2) 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。
类适配器模式的缺点如下:
(1) 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
(2) 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
(3) 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
对象适配器模式的缺点如下:
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用场景
在以下情况下可以考虑使用适配器模式:
(1) 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
(2) 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。