一、 适配器模式
1.1概述
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
适配器模式的实现有两种方式: 类适配器:一次最多只能适配一个适配者类,不能同时适配多个适配者;适配者类不能为最终类;目标抽象类只能为接口,不能为类。 对象适配器:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类;在适配器中置换适配者类的某些方法比较麻烦。
1.2结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:实现目标接口,并将不兼容的接口转换为目标接口。它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
1.3 类适配器模式
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类图如下:
目标(Target)接口
package com.yanyu.Adapter1;
//SD卡的接口
public interface SDCard {
//读取SD卡方法
String readSD();
//写入SD卡功能
void writeSD(String msg);
}
package com.yanyu.Adapter1;
//SD卡实现类
public class SDCardImpl implements SDCard {
public String readSD() {
String msg = "sd card read a msg :hello word SD";
return msg;
}
public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}
适配者(Adaptee)类:
package com.yanyu.Adapter1;
//TF卡接口
public interface TFCard {
//读取TF卡方法
String readTF();
//写入TF卡功能
void writeTF(String msg);
}
package com.yanyu.Adapter1;
//TF卡实现类
public class TFCardImpl implements TFCard {
public String readTF() {
String msg ="tf card read msg : hello word tf card";
return msg;
}
public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}
适配器(Adapter)类
package com.yanyu.Adapter1;
//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {
//继承了TFCardImpl类,实现了TF卡的读写功能,并且实现了SDCard接口,使其兼容SD卡
//实现SD卡的读取方法
public String readSD() {
System.out.println("adapter read tf card "); //打印提示信息
return readTF(); //调用TF卡的读取方法
}
//实现SD卡的写入方法
public void writeSD(String msg) {
System.out.println("adapter write tf card"); //打印提示信息
writeTF(msg); //调用TF卡的写入方法
}
}
客户端类
package com.yanyu.Adapter1;
//电脑类
public class Computer {
public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}
package com.yanyu.Adapter1;
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer(); //创建计算机对象
SDCard sdCard = new SDCardImpl(); //创建SD卡对象
System.out.println(computer.readSD(sdCard)); //在计算机上读取SD卡的内容并打印
System.out.println("------------");
SDAdapterTF adapter = new SDAdapterTF(); //创建SD适配器对象
System.out.println(computer.readSD(adapter)); //在计算机上使用适配器读取TF卡的内容并打印
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
1.4对象适配器模式
实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。类图如下:
代码如下:
类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。
package com.yanyu.Adapter1;
//定义适配器类(SD兼容TF)
//创建适配器对象(SD兼容TF)
public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
package com.yanyu.Adapter1;
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer(); //创建计算机对象
SDCard sdCard = new SDCardImpl(); //创建SD卡对象
System.out.println(computer.readSD(sdCard)); //在计算机上读取SD卡的内容并打印
System.out.println("------------");
TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard); //创建SD适配器对象
System.out.println(computer.readSD(adapter)); //在计算机上使用适配器读取TF卡的内容并打印
}
}
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
1.5 应用场景
适应的场景:
- 系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码;
- 创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
1.6JDK源码解析
在JDK中,有很多使用了适配器模式的地方,其中最常见的是集合框架中的迭代器。迭代器是一种提供对集合元素进行访问的机制,它通常被用于循环遍历集合中的元素。在集合框架中,每个集合都提供了一个迭代器,使得可以使用通用的方式对其进行遍历。
另一个使用适配器模式的例子是JDBC(Java DataBase Connectivity)。JDBC是一种用于连接数据库的API,它提供了一系列的接口。其中,Connection、Statement和ResultSet是最常用的接口。JDBC还提供了一种叫做DriverManager的类,它提供了一个用于建立数据库连接的静态方法getConnection()。在JDBC中,不同的数据库供应商会提供不同的驱动程序,这些驱动程序都实现了JDBC的接口。JDBC驱动程序通常被封装在一个适配器中,使得它们可以与JDBC API协同工作。
二、实验
任务描述
现有一个接口 DataOperation 定义了排序方法 sort(int[]) 和查找方法 search(int[],int),已知类 QuickSort 的 quickSort(int[]) 方法实现了快速排序算法,类 BinarySearch 的 binarySearch(int[],int) 方法实现了二分查找算法。
本关任务:现使用适配器模式设计一个系统,在不修改源代码的情况下将类 QuickSort 和类 BinarySearch 的方法适配到 DataOperation 接口中。
实现方式
- 确保至少有两个类的接口不兼容:一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。一个或多个将受益于使用服务类的客户端类。
- 声明客户端接口, 描述客户端如何与服务交互。
- 创建遵循客户端接口的适配器类。 所有方法暂时都为空。
- 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。
- 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。
- 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。
编程要求
根据提示,在右侧编辑器 Begin-End 内补充 "OperationAdapter.java" 的代码,计算并输出结果。其它文件不需要修改。
测试说明
平台会对你编写的代码进行测试:第一行输入数组个数,第二行输入数组元素,第三行输出需要查询的数。查询的结果 1 表示“找到了”,-1 表示“没有找到”
测试输入:
7
;41 2 58 12 66 98 5
;12
; 预期输出:实现快速排序:
2 5 12 41 58 66 98
实现了二分查找算法:
1
测试输入:
8
;58 40 12 66 77 5 48 23
;10
; 预期输出:实现快速排序:
5 12 23 40 48 58 66 77
实现了二分查找算法:
-1
目标接口
package step1;
public interface DataOperation {
public void sort(int array[]);
public int search(int array[],int key);
}
DataOperation接口定义了客户端代码所期望的操作,即sort和search方法,而适配器将快速排序和二分查找算法适配到这个接口中,使得客户端可以统一调用这两种算法。
适配者(Adaptee)类
package step1;
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
}
}
package step1;
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;
}
}
适配器(Adapter)类
package step1;
/********** Begin *********/
// OperationAdapter类实现了DataOperation接口,将QuickSort和BinarySearch适配到DataOperation接口中
public class OperationAdapter implements DataOperation{
private QuickSort qSort; // 适配者1:快速排序算法
private BinarySearch binarySearch; // 适配者2:二分查找算法
// 构造方法,接收快速排序和二分查找算法对象
public OperationAdapter(QuickSort qSort, BinarySearch binarySearch){
this.qSort = qSort;
this.binarySearch = binarySearch;
}
// 实现DataOperation接口中的sort方法,调用适配者1的快速排序算法
public void sort(int[] array){
qSort.quickSort(array);
}
// 实现DataOperation接口中的search方法,调用适配者2的二分查找算法
public int search(int[] array, int key){
return binarySearch.binarySearch(array, key);
}
}
/********** End *********/
客户端类
package step1;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
// 创建适配者模式的适配器对象,将快速排序和二分查找算法适配到统一的接口
DataOperation dataOperation = new OperationAdapter(new QuickSort(),new BinarySearch());
int i = 0;
Scanner scanner = new Scanner(System.in);
int count = scanner.nextInt();
int[] array = new int[count];
// 读取输入的数组元素
while (scanner.hasNext()) {
array[i++] = scanner.nextInt();
if (i == array.length) {
break;
}
}
int key = scanner.nextInt();
// 使用适配者模式调用快速排序算法
dataOperation.sort(array);
System.out.println("实现快速排序:");
// 输出排序后的数组
for(i = 0; i<array.length; i++){
System.out.print(array[i]+" ");
}
System.out.println("\n"+"实现了二分查找算法:");
// 使用适配者模式调用二分查找算法
System.out.println(dataOperation.search(array, key));
}
}