設計模式-適配器模式(Adapter)-Java

設計模式-適配器模式-Java


目錄




內容

  我的筆記本電腦的工作電壓是20V,而我國的家庭用電是220V,如何讓20V的筆記本電腦能夠在220V的電壓下工作?答案是引入一個電源適配器(AC Adapter),俗稱充電器或變壓器,有了這個電源適配器,生活用電和筆記本電腦即可兼容,如圖0-1所示:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
圖0-1 電源適配器示意圖

1、示例案例-沒有源碼的算法庫

  Sunny軟件公司在很久以前曾開發了一個算法庫,裏面包含了一些常用的算法,例如排序算法和查找算法,在進行各類軟件開發時經常需要重用該算法庫中的算法。在爲某學校開發教務管理系統時,開發人員發現需要對學生成績進行排序和查找,該系統的設計人員已經開發了一個成績操作接口ScoreOperation,在該接口中聲明瞭排序方法sort(int[]) 和查找方法search(int[], int),爲了提高排序和查找的效率,開發人員決定重用算法庫中的快速排序算法類QuickSort和二分查找算法類BinarySearch,其中QuickSort的quickSort(int[])方法實現了快速排序,BinarySearch 的binarySearch (int[], int)方法實現了二分查找。

  由於某些原因,現在Sunny公司開發人員已經找不到該算法庫的源代碼,無法直接通過複製和粘貼操作來重用其中的代碼;部分開發人員已經針對ScoreOperation接口編程,如果再要求對該接口進行修改或要求大家直接使用QuickSort類和BinarySearch類將導致大量代碼需要修改。

  Sunny軟件公司開發人員面對這個沒有源碼的算法庫,遇到一個幸福而又煩惱的問題:如何在既不修改現有接口又不需要任何算法庫代碼的基礎上能夠實現算法庫的重用?

  通過分析,我們不難得知,現在Sunny軟件公司面對的問題有點類似本章最開始所提到的電壓問題,成績操作接口ScoreOperation好比只支持20V電壓的筆記本,而算法庫好比220V的家庭用電,這兩部分都沒有辦法再進行修改,而且它們原本是兩個完全不相關的結構,如圖0-2所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qHnZ0X1c-1591611767294)(./images/TSM_AR.png)]
圖0-2 需協調的兩個系統的結構示意圖

  現在我們需要ScoreOperation接口能夠和已有算法庫一起工作,讓它們在同一個系統中能夠兼容,最好的實現方法是增加一個類似電源適配器一樣的適配器角色,通過適配器來協調這兩個原本不兼容的結構。如何在軟件開發中設計和實現適配器是本章我們將要解決的核心問題,下面就讓我們正式開始學習這種用於解決不兼容結構問題的適配器模式。

2、適配器模式概述

  與電源適配器相似,在適配器模式中引入了一個被稱爲適配器(Adapter)的包裝類,而它所包裝的對象稱爲適配者(Adaptee),即被適配的類。適配器的實現就是把客戶類的請求轉化爲對適配者的相應接口的調用。也就是說:當客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。因此,適配器讓那些由於接口不兼容而不能交互的類可以一起工作。

2.1、適配器模式定義

  適配器模式可以將一個類的接口和另一個類的接口匹配起來,而無須修改原來的適配者接口和抽象目標類接口。適配器模式定義如下:

  • 適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作,其別名爲包裝器(Wrapper)。適配器模式既可以作爲類結構型模式,也可以作爲對象結構型模式。

【注:在適配器模式定義中所提及的接口是指廣義的接口,它可以表示一個方法或者方法的集合。】

2.2、適配器模式要點

  在適配器模式中,我們通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關係的類可以協同工作。根據適配器類與適配者類的關係不同,適配器模式分爲對象適配器和類適配器兩種,在對象適配器模式中,適配器類和適配者類之間是關聯關係;在類適配器模式中,適配器類和適配者類之間是繼承(或實現)關係。在實際開發中,對象適配器的使用頻率更高,對象適配器模式結構如圖2.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H42mNSlG-1591611767295)(./images/objectAdapter.png)]
圖2.2-1 對象適配器模式結構圖

2.3、適配器模式結構圖中角色

  在對象適配器模式結構圖中包含如下幾個角色:

  • Target(目標抽象類):目標抽象類定義客戶端所需接口,可是一個抽象類或者接口,也可以是具體類。
  • Adapter(適配器類):適配器可以調用另外一個接口,作爲一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它通過繼承Target並關聯一個Adaptee對象使二者產生聯繫。
  • Adaptee(適配者類):適配者即被適配的角色,它定義 了已經存在的接口,這個接口需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的源代碼。

2.4、適配器模式使用

  根據對象適配器模式結構圖,在對象適配器中,客戶端需要調用request()方法,而適配者類Adaptee沒有該方法,但是它所提供的specificRequest()方法卻是客戶端所需要的。爲了使客戶端能夠使用適配者類,需要提供一個包裝類Adapter,即適配器類。這個包裝類包裝了一個適配者的實例,從而將客戶端與適配者銜接起來,在適配器的request()方法中調用適配者的specificRequest()方法。因爲適配器類與適配者類是關聯關係(也可稱之爲委派關係),所以這種適配器模式稱爲對象適配器模式。典型的對象適配器代碼2.4-1如下所示:

class Adapter extends Target {
	private Adaptee adaptee; //維持一個對適配者對象的引用
	public Adapter(Adaptee adaptee) {
		this.adaptee=adaptee;
	}
	public void request() {
		adaptee.specificRequest(); //轉發調用
	}
}

3、對象適配器模式

  • (沒有源碼的算法庫)完整解決方案

  Sunny軟件公司開發人員決定使用適配器模式來重用算法庫中的算法,其基本結構如圖3-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uFbwfGIi-1591611767296)(./images/AR.png)]
圖3-1 算法庫重用結構圖

在圖3-1中,ScoreOperation接口充當抽象目標,QuickSort和BinarySearch類充當適配者,OperationAdapter充當適配器。完整代碼如下所示:

  • 成績操作接口代碼3-1:

      package adapter;
    
      //抽象成績操作類:目標接口
      public interface ScoreOperation {
      	public void sort(int[] array);
      	public int search(int[] array, int key);
      }
    
  • 快速排序QuickSort類代碼3-2:

      package adapter;
    
      //快速排序類:適配者
      public class QuickSort {
    
      	public void quickSort(int[] array) {
      		sort(array, 0, array.length - 1);
      	}
    
      	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 tem = a[i];
      		a[i] = a[j];
      		a[j] = tem;
      	}
      }
    
  • 二分查找BinarySearch類代碼3-3:

      package adapter;
    
      //二分查找類:適配者
      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
      	}
      }
    
  • 操作適配器OperationAdapter類代碼3-4:

      package adapter;
    
      //操作適配器:適配器
      public class OperationAdapter implements ScoreOperation{
    
      	private QuickSort sortObj;
      	private BinarySearch searchObj;
    
    
      	public OperationAdapter() {
      		sortObj = new QuickSort();
      		searchObj = new BinarySearch();
      	}
    
      	@Override
      	public void sort(int[] array) {
      		this.sortObj.quickSort(array);
      	}
    
      	@Override
      	public int search(int[] array, int key) {
      		return this.searchObj.binarySearch(array, key);
      	}
      }
    
  • 工具類代碼3-5:

      package adapter;
    
      import java.util.Properties;
    
      public class Utils {
      	public static OperationAdapter getOperationAdapter() {
      		try {
      			Properties prop = new Properties();
      			prop.load(Utils.class.getClassLoader().getResourceAsStream("ar.properties"));
      			String className = prop.getProperty("className");
      			OperationAdapter adapter = (OperationAdapter)(Class.forName(className).newInstance());
      			return adapter;
      		}catch (Exception e) {
      			e.printStackTrace();
      			return null;
      		}
      	}
      }
    
  • 客戶端類代碼3-6:

      package adapter;
    
      import java.util.Arrays;
    
      public class Client {
      	public static void main(String args[]) {
      		ScoreOperation operation; //針對抽象目標接口編程
      		operation = Utils.getOperationAdapter(); //讀取配置文件,反射生成對象
      		int scores[] = {84,76,50,69,90,91,88,96}; //定義成績數組
      		int score;
      		System.out.println("成績排序結果:");
      		operation.sort(scores);
      		//遍歷輸出成績
      		System.out.println(Arrays.toString(scores));
    
      		System.out.println("查找成績90:");
      		score = operation.search(scores,90);
      		if (score != -1) {
      			System.out.println("找到成績90。");
      		}
      		else {
      			System.out.println("沒有找到成績90。");
      		}
    
      		System.out.println("查找成績92:");
      		score = operation.search(scores,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。
    

  在本實例中使用了對象適配器模式,同時引入了配置文件,將適配器類的類名存儲在配置文件中。如果需要使用其他排序算法類和查找算法類,可以增加一個新的適配器類,使用新的適配器來適配新的算法,原有代碼無須修改。通過引入配置文件和反射機制,可以在不修改
客戶端代碼的情況下使用新的適配器,無須修改源代碼,符合“開閉原則”。

4、類適配器

  除了對象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對象適配器模式最大的區別在於適配器和適配者之間的關係不同,對象適配器模式中適配器和適配者之間是關聯關係,而類適配器模式中適配器和適配者是繼承關係,類適配器模式結構如圖4-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-X06sltak-1591611767297)(./images/classAdapter.png)]
圖4-1 類適配器模式結構圖

  根據類適配器模式結構圖,適配器類實現了抽象目標類接口Target,並繼承了適配者類,在適配器類的request()方法中調用所繼承的適配者類的specificRequest()方法,實現了適配。

  典型的類適配器代碼如下所示:

class Adapter extends Adaptee implements Target {
	public void request() {
		specificRequest();
	}
}

  由於Java、C#等語言不支持多重類繼承,因此類適配器的使用受到很多限制,例如如果目標抽象類Target不是接口,而是一個類,就無法使用類適配器;此外,如果適配者Adapter爲最終(Final)類,也無法使用類適配器。在Java等面向對象編程語言中,大部分情況下我們使用的是對象適配器,類適配器較少使用。

5、缺省適配器

  缺省適配器模式是適配器模式的一種變體,其應用也較爲廣泛。缺省適配器模式的定義如下:

5.1、定義

  • 缺省適配器(Default Adapter Pattern):當不需要實現一個接口所提供的所有方法時,可先設計一個抽象類實現該接口,併爲接口中每一個方法提供一個默認實現(空方法),那麼該抽象類的子類可以選擇性地覆蓋父類的某些方法來實現需求,它適用於不想使用一個接口中所有方法的情況,又稱爲單接口適配器模式。

5.2、結構圖

  缺省適配器模式結構如圖5.2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HkFStXpQ-1591611767299)(./images/defaultAdapter.png)]

5.3、角色

  在缺省適配器模式中,包含如下三個角色:

  • ServiceInterface(適配者接口):它是一個接口,通常在該接口中聲明瞭大量的方法。
  • AbstractService(缺省適配器類):它是缺省適配器模式的核心類,使用空方法的形式實現了在ServiceInterface接口中聲明的方法。通常將它定義爲抽象類,因爲對它進行實例化沒有任何意義。
  • ConcreteService(具體業務類):它是缺省適配器類的子類,在沒有引入適配器之前它需要實現適配者接口,因此需要實現在適配者接口中定義的所有方法,而對於一些無須使用的方法也不得不提供空實現。有了缺省適配器之後,可以之間繼承該適配器類,根據需要有選擇性地覆蓋在適配器類中定義的方法。

5.4、應用

  在JDK類庫的事件處理包java.awt.event中廣泛使用了缺省適配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。下面我們以處理窗口事件爲例來進行說明:在Java語言中,一般我們可以使用兩種方式來實現窗口事件處理類,一種是通過實現WindowListener接口,另一種是通過繼承WindowAdapter適配器類。如果是使用第一種方式,直接實現WindowListener接口,事件處理類需要實現在該接口中定義的七個方法,而對於大部分需求可能只需要實現一兩個方法,其他方法都無須實現,但由於語言特性我們不得不爲其他方法也提供一個簡單的實現(通常是空實現),這給使用帶來了麻煩。而使用缺省適配器模式就可以很好地解決這一問題,在JDK中提供了一個適配器類WindowAdapter來實現WindowListener接口,該適配器類爲接口中的每一個方法都提供了一個空實現,此時事件處理類可以繼承WindowAdapter類,而無須再爲接口中的每個方法都提供實現。如圖5.4-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-V2qV5jwF-1591611767300)(./images/windowAdapter.png)]
圖5.4-1 WindowListener 和 WindowAdapter 結構圖

6、總結

  適配器模式將現有接口轉化爲客戶類所期望的接口,實現了對現有類的複用,它是一種使用頻率非常高的設計模式,在軟件開發中得以廣泛應用,在Spring等開源框架、驅動程序設計(如JDBC中的數據庫驅動程序)中也使用了適配器模式。

6.1、優缺點

  無論是對象適配器模式還是類適配器模式都具有如下優點:

  • (1):將模板類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,無須修改原有結構。
  • (2):增加了類的透明性和複用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的複用性,同一個適配者類可以在多個不同的系統中複用。
  • (3):靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。

  具體來說,類適配器模式還有如下優點:

  • 由於適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強

  主要缺點:
類適配器模式的缺點如下:

  • (1) 對於Java、C#等不支持多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者;
  • (2) 適配者類不能爲最終類,如在Java中不能爲final類,C#中不能爲sealed類;
  • (3) 在Java、C#等語言中,類適配器模式中的目標抽象類只能爲接口,不能爲類,其使用有一定的侷限性。

  對象適配器模式的缺點如下:

  • 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法,可以先做一個適配者類的子類,將適配者類的方法置換掉,然後再把適配者類的子類當做真正的適配者進行適配,實現過程較爲複雜。

6.2、適用場景

  在以下情況下可以考慮使用適配器模式:

  • (1) 系統需要使用一些現有的類,而這些類的接口(如方法名)不符合系統的需要,甚至沒有這些類的源代碼。
  • (2) 想創建一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。

後記

  參考文獻:Java設計模式(劉偉).pdf。持續更新,歡迎交流,本人QQ:806797785

前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章