前言:設(shè)計模式之結(jié)構(gòu)型模式
軟件模式與具體的應(yīng)用領(lǐng)域無關(guān),也就是說無論你從事的是移動應(yīng)用開發(fā)、桌面應(yīng)用開發(fā)、Web 應(yīng)用開發(fā)還是嵌入式軟件的開發(fā),都可以使用軟件模式。
在軟件模式中,設(shè)計模式是研究最為深入的分支,設(shè)計模式用于在特定的條件下為一些重復(fù)出現(xiàn)的軟件設(shè)計問題提供合理的、有效的解決方案,它融合了眾多專家的設(shè)計經(jīng)驗,已經(jīng)在成千上萬的軟件中得以應(yīng)用。1995 年,GoF 將收集和整理好的 23 種設(shè)計模式匯編成《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書,該書的出版也標(biāo)志著設(shè)計模式正式成為面向?qū)ο?Object Oriented)軟件工程的一個重要研究分支。
結(jié)構(gòu)型設(shè)計模式統(tǒng)共七種:適配器模式、橋接模式、組合模式、裝飾模式、外觀模式、享元模式和代理模式。
今天來記錄適配器模式。
記錄結(jié)構(gòu)
1.用例引入(要解決的問題)
2.采用適配器模式解決問題
2.1 適配器模式概念及類圖表示(概念,UML類圖表示 )
2.2 適配器模式詳細(xì)解決方案(代碼層面)
3.適配器模式分類
3.1 對象適配器
3.2 類適配器
3.3 缺省適配器
4.適配器模式優(yōu)缺點總結(jié)
1.用例引入(要解決的問題)
我的筆記本電腦的工作電壓是 20 V,而我國的家庭用電是 220 V,如何讓 20 V 的筆記本電腦能夠在 220 V 的電壓下工作?答案是引入一個電源適配器(AC Adapter),俗稱充電器或變壓器,有了這個電源適配器,生活用電和筆記本電腦即可兼容,如圖所示:
簡單來說:
在軟件開發(fā)中,有時也存在類似這種不兼容的情況,我們也可以像引入一個電源適配器一樣引入一個稱之為適配器的角色來協(xié)調(diào)這些存在不兼容的結(jié)構(gòu),這種設(shè)計方案即為適配器模式。
要解決的問題:
沒有源碼的算法庫
YY軟件公司在很久以前曾開發(fā)了一個算法庫,里面包含了一些常用的算法,例如排序算法和查找算法,在進(jìn)行各類軟件開發(fā)時經(jīng)常需要重用該算法庫中的算法。在為某學(xué)校開發(fā)教務(wù)管理系統(tǒng)時,開發(fā)人員發(fā)現(xiàn)需要對學(xué)生成績進(jìn)行排序和查找,該系統(tǒng)的設(shè)計人員已經(jīng)開發(fā)了一個成績操作接口 ScoreOperation,在該接口中聲明了排序方法 sort(int[]) 和查找方法 search(int[], int),為了提高排序和查找的效率,開發(fā)人員決定重用算法庫中的快速排序算法類 QuickSort 和二分查找算法類 BinarySearch,其中 QuickSort 的 quickSort(int[]) 方法實現(xiàn)了快速排序,BinarySearch 的 binarySearch (int[], int) 方法實現(xiàn)了二分查找。
由于某些原因,現(xiàn)在 Y Y公司開發(fā)人員已經(jīng)找不到該算法庫的源代碼,無法直接通過復(fù)制和粘貼操作來重用其中的代碼;部分開發(fā)人員已經(jīng)針對 ScoreOperation 接口編程,如果再要求對該接口進(jìn)行修改或要求大家直接使用 QuickSort 類和 BinarySearch 類將導(dǎo)致大量代碼需要修改。
Sunny 軟件公司開發(fā)人員面對這個沒有源碼的算法庫,遇到一個幸福而又煩惱的問題:如何在既不修改現(xiàn)有接口又不需要任何算法庫代碼的基礎(chǔ)上能夠?qū)崿F(xiàn)算法庫的重用?
通過分析,我們不難得知,現(xiàn)在 Sunny 軟件公司面對的問題有點類似本章最開始所提到的電壓問題,成績操作接口 ScoreOperation 好比只支持 20 V 電壓的筆記本,而算法庫好比 220 V 的家庭用電,這兩部分都沒有辦法再進(jìn)行修改,而且它們原本是兩個完全不相關(guān)的結(jié)構(gòu),如圖所示:
現(xiàn)在我們需要 ScoreOperation 接口能夠和已有算法庫一起工作,讓它們在同一個系統(tǒng)中能夠兼容,最好的實現(xiàn)方法是增加一個類似電源適配器一樣的適配器角色,通過適配器來協(xié)調(diào)這兩個原本不兼容的結(jié)構(gòu)。如何在軟件開發(fā)中設(shè)計和實現(xiàn)適配器是本章我們將要解決的核心問題,下面就讓我們正式開始學(xué)習(xí)這種用于解決不兼容結(jié)構(gòu)問題的適配器模式。
2.采用適配器模式解決問題
2.1 適配器模式概念及類圖表示(概念,UML類圖表示 )
與電源適配器相似,在適配器模式中引入了一個被稱為適配器(Adapter)的包裝類,而它所包裝的對象稱為適配者(Adaptee),即被適配的類。適配器的實現(xiàn)就是把客戶類的請求轉(zhuǎn)化為對適配者的相應(yīng)接口的調(diào)用。也就是說:當(dāng)客戶類調(diào)用適配器的方法時,其實在適配器類的內(nèi)部將調(diào)用適配者類的方法,而這個過程對客戶類是透明的,客戶類并不直接訪問適配者類。因此,適配器讓那些由于接口不兼容而不能交互的類可以一起工作。
適配器模式可以將一個類的接口和另一個類的接口匹配起來,而無須修改原來的適配者接口和抽象目標(biāo)類接口。適配器模式定義如下:
適配器模式(Adapter Pattern):將一個接口轉(zhuǎn)換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結(jié)構(gòu)型模式,也可以作為對象結(jié)構(gòu)型模式。
適配器模式類圖表示:
在適配器模式中,我們通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關(guān)系的類可以協(xié)同工作。根據(jù)適配器類與適配者類的關(guān)系不同,適配器模式可分為對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關(guān)聯(lián)關(guān)系;在類適配器模式中,適配器與適配者之間是繼承(或?qū)崿F(xiàn))關(guān)系。在實際開發(fā)中,對象適配器的使用頻率更高,對象適配器模式結(jié)構(gòu)如圖所示:
在對象適配器模式結(jié)構(gòu)圖中包含如下幾個角色:
Target(目標(biāo)抽象類):目標(biāo)抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。
- Target(目標(biāo)抽象類):目標(biāo)抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。
- Adapter(適配器類):適配器可以調(diào)用另一個接口,作為一個轉(zhuǎn)換器,對Adaptee和Target進(jìn)行適配,適配器類是適配器模式的核心,在對象適配器中,它通過繼承Target并關(guān)聯(lián)一個Adaptee對象使二者產(chǎn)生聯(lián)系。
- Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經(jīng)存在的接口,這個接口需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業(yè)務(wù)方法,在某些情況下可能沒有適配者類的源代碼。
** 根據(jù)對象適配器模式結(jié)構(gòu)圖,在對象適配器中,客戶端需要調(diào)用 request() 方法,而適配者類 Adaptee 沒有該方法,但是它所提供的 specificRequest() 方法卻是客戶端所需要的。為了使客戶端能夠使用適配者類,需要提供一個包裝類 Adapter,即適配器類。這個包裝類包裝了一個適配者的實例,從而將客戶端與適配者銜接起來,在適配器的 request() 方法中調(diào)用適配者的 specificRequest() 方法。因為適配器類與適配者類是關(guān)聯(lián)關(guān)系(也可稱之為委派關(guān)系),所以這種適配器模式稱為對象適配器模式。**
2.2 適配器模式詳細(xì)解決方案(代碼層面)
YY軟件公司開發(fā)人員決定使用適配器模式來重用算法庫中的算法,其基本結(jié)構(gòu)如圖 9-4 所示:
在圖中,ScoreOperation 接口充當(dāng)抽象目標(biāo),QuickSort 和 BinarySearch 類充當(dāng)適配者,OperationAdapter 充當(dāng)適配器。完整代碼如下所示:
抽象成績操作類:目標(biāo)接口
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[]) {
//調(diào)用適配者類QuickSort的排序方 法
return sortObj.quickSort(array);
}
public int search(int array[],int key) {
return searchObj.binarySearch(array,key); //調(diào)用適配者類BinarySearch的查找方法
}
}
為了讓系統(tǒng)具備良好的靈活性和可擴(kuò)展性,我們引入了工具類 XMLUtil 和配置文件,其中,XMLUtil 類的代碼如下所示:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
class XMLUtil {
public static Object getBean() {try {
//創(chuàng)建文檔對象DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("config.xml"));
//獲取包含類名的文本節(jié)點
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; //針對抽象目標(biāo)接口編程
operation = (ScoreOperation)XMLUtil.getBean(); //讀取配置文件,反射生成對象
int scores[] = {84,76,50,69,90,91,88,96}; //定義成績數(shù)組
int result[];
int score;
System.out.println("成績排序結(jié)果:");
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。");
}
}
}
運(yùn)行結(jié)果如下:
成績排序結(jié)果:
50,69,76,84,88,90,91,96,
查找成績90:
找到成績90。
查找成績92:
沒有找到成績92。
在本實例中使用了對象適配器模式,同時引入了配置文件,將適配器類的類名存儲在配置文件中。如果需要使用其他排序算法類和查找算法類,可以增加一個新的適配器類,使用新的適配器來適配新的算法,原有代碼無須修改。通過引入配置文件和反射機(jī)制,可以在不修改客戶端代碼的情況下使用新的適配器,無須修改源代碼,符合“開閉原則”。
3.適配器模式分類
3.1 對象適配器
上文中記錄的便是對象適配器,可以參照上問來進(jìn)行強(qiáng)化記憶。
3.2 類適配器
除了對象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對象適配器模式最大的區(qū)別在于適配器和適配者之間的關(guān)系不同,對象適配器模式中適配器和適配者之間是關(guān)聯(lián)關(guān)系,而類適配器模式中適配器和適配者是繼承關(guān)系,類適配器模式結(jié)構(gòu)如圖所示:
根據(jù)類適配器模式結(jié)構(gòu)圖,適配器類實現(xiàn)了抽象目標(biāo)類接口 Target,并繼承了適配者類,在適配器類的 request() 方法中調(diào)用所繼承的適配者類的 specificRequest() 方法,實現(xiàn)了適配。
典型的類適配器代碼如下所示:
class Adapter extends Adaptee implements Target {
public void request() {
specificRequest(); //此方法是Adaptee類中定義的實例方法
}
}
由于Java、C#等語言不支持多重類繼承,因此類適配器的使用受到很多限制,例如如果目標(biāo)抽象類Target不是接口,而是一個類,就無法使用類適配器;此外,如果適配者Adapter為最終(Final)類,也無法使用類適配器。在Java等面向?qū)ο缶幊陶Z言中,大部分情況下我們使用的是對象適配器,類適配器較少使用。
3.3 缺省適配器
缺省適配器模式是適配器模式的一種變體,其應(yīng)用也較為廣泛。缺省適配器模式的定義如下: 缺省適配器模式(Default Adapter Pattern):當(dāng)不需要實現(xiàn)一個接口所提供的所有方法時,可先設(shè)計一個抽象類實現(xiàn)該接口,并為接口中每個方法提供一個默認(rèn)實現(xiàn)(空方法),那么該抽象類的子類可以選擇性地覆蓋父類的某些方法來實現(xiàn)需求,它適用于不想使用一個接口中的所有方法的情況,又稱為單接口適配器模式。
缺省適配器模式結(jié)構(gòu)如圖所示:
在缺省適配器模式中,包含如下三個角色:
- ServiceInterface(適配者接口):它是一個接口,通常在該接口中聲明了大量的方法。
- AbstractServiceClass(缺省適配器類):它是缺省適配器模式的核心類,使用空方法的形式實現(xiàn)了在 ServiceInterface 接口中聲明的方法。通常將它定義為抽象類,因為對它進(jìn)行實例化沒有任何意義。
- ConcreteServiceClass(具體業(yè)務(wù)類):它是缺省適配器類的子類,在沒有引入適配器之前,它需要實現(xiàn)適配者接口,因此需要實現(xiàn)在適配者接口中定義的所有方法,而對于一些無須使用的方法也不得不提供空實現(xiàn)。在有了缺省適配器之后,可以直接繼承該適配器類,根據(jù)需要有選擇性地覆蓋在適配器類中定義的方法。
4.適配器模式優(yōu)缺點總結(jié)
無論是對象適配器模式還是類適配器模式都具有如下優(yōu)點:
將目標(biāo)類和適配者類解耦,通過引入一個適配器類來重用現(xiàn)有的適配者類,無須修改原有結(jié)構(gòu)。
增加了類的透明性和復(fù)用性,將具體的業(yè)務(wù)實現(xiàn)過程封裝在適配者類中,對于客戶端類而言是透明的,而且提高了適配者的復(fù)用性,同一個適配者類可以在多個不同的系統(tǒng)中復(fù)用。
靈活性和擴(kuò)展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎(chǔ)上增加新的適配器類,完全符合“開閉原則”。
類適配器模式還有如下優(yōu)點:
- 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強(qiáng)。
對象適配器模式還有如下優(yōu)點:
一個對象適配器可以把多個不同的適配者適配到同一個目標(biāo);
可以適配一個適配者的子類,由于適配器和適配者之間是關(guān)聯(lián)關(guān)系,根據(jù)“里氏代換原則”,適配者的子類也可通過該適配器進(jìn)行適配。
類適配器模式的缺點:
對于 Java、C# 等不支持多重類繼承的語言,一次最多只能適配一個適配者類,不能同時適配多個適配者;
適配者類不能為最終類,如在 Java 中不能為 final 類,C# 中不能為 sealed 類;
在 Java、C# 等語言中,類適配器模式中的目標(biāo)抽象類只能為接口,不能為類,其使用有一定的局限性。
對象適配器模式的缺點:
- 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法,可以先做一個適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當(dāng)做真正的適配者進(jìn)行適配,實現(xiàn)過程較為復(fù)雜。
適用場景
在以下情況下可以考慮使用適配器模式:
- 系統(tǒng)需要使用一些現(xiàn)有的類,而這些類的接口(如方法名)不符合系統(tǒng)的需要,甚至沒有這些類的源代碼。
- 想創(chuàng)建一個可以重復(fù)使用的類,用于與一些彼此之間沒有太大關(guān)聯(lián)的一些類,包括一些可能在將來引進(jìn)的類一起工作。
博客搬家:大坤的個人博客
歡迎評論哦~