結(jié)構(gòu)型設(shè)計(jì)模式(一) --適配器模式

前言:設(shè)計(jì)模式之結(jié)構(gòu)型模式
軟件模式與具體的應(yīng)用領(lǐng)域無關(guān),也就是說無論你從事的是移動(dòng)應(yīng)用開發(fā)、桌面應(yīng)用開發(fā)、Web 應(yīng)用開發(fā)還是嵌入式軟件的開發(fā),都可以使用軟件模式。
在軟件模式中,設(shè)計(jì)模式是研究最為深入的分支,設(shè)計(jì)模式用于在特定的條件下為一些重復(fù)出現(xiàn)的軟件設(shè)計(jì)問題提供合理的、有效的解決方案,它融合了眾多專家的設(shè)計(jì)經(jīng)驗(yàn),已經(jīng)在成千上萬的軟件中得以應(yīng)用。1995 年,GoF 將收集和整理好的 23 種設(shè)計(jì)模式匯編成《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書,該書的出版也標(biāo)志著設(shè)計(jì)模式正式成為面向?qū)ο?Object Oriented)軟件工程的一個(gè)重要研究分支。

結(jié)構(gòu)型設(shè)計(jì)模式統(tǒng)共七種:適配器模式、橋接模式、組合模式、裝飾模式、外觀模式、享元模式和代理模式。
今天來記錄適配器模式

記錄結(jié)構(gòu)
1.用例引入(要解決的問題)
2.采用適配器模式解決問題
2.1 適配器模式概念及類圖表示(概念,UML類圖表示 )
2.2 適配器模式詳細(xì)解決方案(代碼層面)
3.適配器模式分類
3.1 對(duì)象適配器
3.2 類適配器
3.3 缺省適配器
4.適配器模式優(yōu)缺點(diǎn)總結(jié)

1.用例引入(要解決的問題)


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

簡(jiǎn)單來說:
在軟件開發(fā)中,有時(shí)也存在類似這種不兼容的情況,我們也可以像引入一個(gè)電源適配器一樣引入一個(gè)稱之為適配器的角色來協(xié)調(diào)這些存在不兼容的結(jié)構(gòu),這種設(shè)計(jì)方案即為適配器模式。

要解決的問題:
沒有源碼的算法庫
YY軟件公司在很久以前曾開發(fā)了一個(gè)算法庫,里面包含了一些常用的算法,例如排序算法和查找算法,在進(jìn)行各類軟件開發(fā)時(shí)經(jīng)常需要重用該算法庫中的算法。在為某學(xué)校開發(fā)教務(wù)管理系統(tǒng)時(shí),開發(fā)人員發(fā)現(xiàn)需要對(duì)學(xué)生成績(jī)進(jìn)行排序和查找,該系統(tǒng)的設(shè)計(jì)人員已經(jīng)開發(fā)了一個(gè)成績(jī)操作接口 ScoreOperation,在該接口中聲明了排序方法 sort(int[]) 和查找方法 search(int[], int),為了提高排序和查找的效率,開發(fā)人員決定重用算法庫中的快速排序算法類 QuickSort 和二分查找算法類 BinarySearch,其中 QuickSort 的 quickSort(int[]) 方法實(shí)現(xiàn)了快速排序,BinarySearch 的 binarySearch (int[], int) 方法實(shí)現(xiàn)了二分查找。
由于某些原因,現(xiàn)在 Y Y公司開發(fā)人員已經(jīng)找不到該算法庫的源代碼,無法直接通過復(fù)制和粘貼操作來重用其中的代碼;部分開發(fā)人員已經(jīng)針對(duì) ScoreOperation 接口編程,如果再要求對(duì)該接口進(jìn)行修改或要求大家直接使用 QuickSort 類和 BinarySearch 類將導(dǎo)致大量代碼需要修改。
Sunny 軟件公司開發(fā)人員面對(duì)這個(gè)沒有源碼的算法庫,遇到一個(gè)幸福而又煩惱的問題:如何在既不修改現(xiàn)有接口又不需要任何算法庫代碼的基礎(chǔ)上能夠?qū)崿F(xiàn)算法庫的重用?
通過分析,我們不難得知,現(xiàn)在 Sunny 軟件公司面對(duì)的問題有點(diǎn)類似本章最開始所提到的電壓?jiǎn)栴},成績(jī)操作接口 ScoreOperation 好比只支持 20 V 電壓的筆記本,而算法庫好比 220 V 的家庭用電,這兩部分都沒有辦法再進(jìn)行修改,而且它們?cè)臼莾蓚€(gè)完全不相關(guān)的結(jié)構(gòu),如圖所示:

問題示意圖

現(xiàn)在我們需要 ScoreOperation 接口能夠和已有算法庫一起工作,讓它們?cè)谕粋€(gè)系統(tǒng)中能夠兼容,最好的實(shí)現(xiàn)方法是增加一個(gè)類似電源適配器一樣的適配器角色,通過適配器來協(xié)調(diào)這兩個(gè)原本不兼容的結(jié)構(gòu)。如何在軟件開發(fā)中設(shè)計(jì)和實(shí)現(xiàn)適配器是本章我們將要解決的核心問題,下面就讓我們正式開始學(xué)習(xí)這種用于解決不兼容結(jié)構(gòu)問題的適配器模式。

2.采用適配器模式解決問題


2.1 適配器模式概念及類圖表示(概念,UML類圖表示 )

   與電源適配器相似,在適配器模式中引入了一個(gè)被稱為適配器(Adapter)的包裝類,而它所包裝的對(duì)象稱為適配者(Adaptee),即被適配的類。適配器的實(shí)現(xiàn)就是把客戶類的請(qǐng)求轉(zhuǎn)化為對(duì)適配者的相應(yīng)接口的調(diào)用。也就是說:當(dāng)客戶類調(diào)用適配器的方法時(shí),其實(shí)在適配器類的內(nèi)部將調(diào)用適配者類的方法,而這個(gè)過程對(duì)客戶類是透明的,客戶類并不直接訪問適配者類。因此,適配器讓那些由于接口不兼容而不能交互的類可以一起工作。

適配器模式可以將一個(gè)類的接口和另一個(gè)類的接口匹配起來,而無須修改原來的適配者接口和抽象目標(biāo)類接口。適配器模式定義如下:
適配器模式(Adapter Pattern):將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結(jié)構(gòu)型模式,也可以作為對(duì)象結(jié)構(gòu)型模式。
適配器模式類圖表示:
在適配器模式中,我們通過增加一個(gè)新的適配器類來解決接口不兼容的問題,使得原本沒有任何關(guān)系的類可以協(xié)同工作。根據(jù)適配器類與適配者類的關(guān)系不同,適配器模式可分為對(duì)象適配器和類適配器兩種,在對(duì)象適配器模式中,適配器與適配者之間是關(guān)聯(lián)關(guān)系;在類適配器模式中,適配器與適配者之間是繼承(或?qū)崿F(xiàn))關(guān)系。在實(shí)際開發(fā)中,對(duì)象適配器的使用頻率更高,對(duì)象適配器模式結(jié)構(gòu)如圖所示:

對(duì)象適配器類圖.png

在對(duì)象適配器模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
Target(目標(biāo)抽象類):目標(biāo)抽象類定義客戶所需接口,可以是一個(gè)抽象類或接口,也可以是具體類。

  • Target(目標(biāo)抽象類):目標(biāo)抽象類定義客戶所需接口,可以是一個(gè)抽象類或接口,也可以是具體類。
  • Adapter(適配器類):適配器可以調(diào)用另一個(gè)接口,作為一個(gè)轉(zhuǎn)換器,對(duì)Adaptee和Target進(jìn)行適配,適配器類是適配器模式的核心,在對(duì)象適配器中,它通過繼承Target并關(guān)聯(lián)一個(gè)Adaptee對(duì)象使二者產(chǎn)生聯(lián)系。
  • Adaptee(適配者類):適配者即被適配的角色,它定義了一個(gè)已經(jīng)存在的接口,這個(gè)接口需要適配,適配者類一般是一個(gè)具體類,包含了客戶希望使用的業(yè)務(wù)方法,在某些情況下可能沒有適配者類的源代碼。

** 根據(jù)對(duì)象適配器模式結(jié)構(gòu)圖,在對(duì)象適配器中,客戶端需要調(diào)用 request() 方法,而適配者類 Adaptee 沒有該方法,但是它所提供的 specificRequest() 方法卻是客戶端所需要的。為了使客戶端能夠使用適配者類,需要提供一個(gè)包裝類 Adapter,即適配器類。這個(gè)包裝類包裝了一個(gè)適配者的實(shí)例,從而將客戶端與適配者銜接起來,在適配器的 request() 方法中調(diào)用適配者的 specificRequest() 方法。因?yàn)檫m配器類與適配者類是關(guān)聯(lián)關(guān)系(也可稱之為委派關(guān)系),所以這種適配器模式稱為對(duì)象適配器模式。**

2.2 適配器模式詳細(xì)解決方案(代碼層面)

YY軟件公司開發(fā)人員決定使用適配器模式來重用算法庫中的算法,其基本結(jié)構(gòu)如圖 9-4 所示:

詳細(xì)解決方案 UML類圖--算法庫重用.png

在圖中,ScoreOperation 接口充當(dāng)抽象目標(biāo),QuickSort 和 BinarySearch 類充當(dāng)適配者,OperationAdapter 充當(dāng)適配器。完整代碼如下所示:
抽象成績(jī)操作類:目標(biāo)接口

interface ScoreOperation {
    public int[] sort(int array[]); //成績(jī)排序
    public int search(int array[],int key); //成績(jī)查找
}

快速排序類:適配者= 被適配的類

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對(duì)象private    
     BinarySearch searchObj; //定義適配者BinarySearch對(duì)象
     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)建文檔對(duì)象DocumentBuilderFactory dFactory =  DocumentBuilderFactory.newInstance();
     DocumentBuilder builder = dFactory.newDocumentBuilder();
     Document doc;
     doc = builder.parse(new File("config.xml"));
     //獲取包含類名的文本節(jié)點(diǎn)
     NodeList nl = doc.getElementsByTagName("className");
     Node  classNode=nl.item(0).getFirstChild();
     String cName=classNode.getNodeValue();
     //通過類名生成實(shí)例對(duì)象并將其返回Class c=Class.forName(cName);
     Object obj=c.newInstance();
            return obj;
      }catch(Exception e) {
            e.printStackTrace();
            return null;
      }
}

配置文件 config.xml 中存儲(chǔ)了適配器類的類名,代碼如下所示:

<?xml version="1.0"?>
<config>
       <className>OperationAdapter</className>
</config>

編寫如下客戶端測(cè)試代碼:

class Client {
    public static void main(String args[]) {
        ScoreOperation operation;  //針對(duì)抽象目標(biāo)接口編程
        operation = (ScoreOperation)XMLUtil.getBean(); //讀取配置文件,反射生成對(duì)象
        int scores[] = {84,76,50,69,90,91,88,96}; //定義成績(jī)數(shù)組
        int result[];
        int score;
        System.out.println("成績(jī)排序結(jié)果:");
        result = operation.sort(scores);
        //遍歷輸出成績(jī)
        for(int i : scores) {
            System.out.print(i + ",");
        }
        System.out.println();
        System.out.println("查找成績(jī)90:");
        score = operation.search(result,90);
        if (score != -1) {
            System.out.println("找到成績(jī)90。");
        }
        else {
            System.out.println("沒有找到成績(jī)90。");
        }
        System.out.println("查找成績(jī)92:");
        score = operation.search(result,92);
        if (score != -1) {
            System.out.println("找到成績(jī)92。");
        }
        else {
            System.out.println("沒有找到成績(jī)92。");
        }
    }
}

運(yùn)行結(jié)果如下:

成績(jī)排序結(jié)果:
50,69,76,84,88,90,91,96,
查找成績(jī)90:
找到成績(jī)90。
查找成績(jī)92:
沒有找到成績(jī)92。

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

3.適配器模式分類


3.1 對(duì)象適配器
上文中記錄的便是對(duì)象適配器,可以參照上問來進(jìn)行強(qiáng)化記憶。
3.2 類適配器
除了對(duì)象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對(duì)象適配器模式最大的區(qū)別在于適配器和適配者之間的關(guān)系不同,對(duì)象適配器模式中適配器和適配者之間是關(guān)聯(lián)關(guān)系,而類適配器模式中適配器和適配者是繼承關(guān)系,類適配器模式結(jié)構(gòu)如圖所示:

類適配器結(jié)構(gòu)圖.jpg

根據(jù)類適配器模式結(jié)構(gòu)圖,適配器類實(shí)現(xiàn)了抽象目標(biāo)類接口 Target,并繼承了適配者類,在適配器類的 request() 方法中調(diào)用所繼承的適配者類的 specificRequest() 方法,實(shí)現(xiàn)了適配。
典型的類適配器代碼如下所示:

class Adapter extends Adaptee implements Target {  
    public void request() {  
        specificRequest();  //此方法是Adaptee類中定義的實(shí)例方法
    } 
} 

由于Java、C#等語言不支持多重類繼承,因此類適配器的使用受到很多限制,例如如果目標(biāo)抽象類Target不是接口,而是一個(gè)類,就無法使用類適配器;此外,如果適配者Adapter為最終(Final)類,也無法使用類適配器。在Java等面向?qū)ο缶幊陶Z言中,大部分情況下我們使用的是對(duì)象適配器,類適配器較少使用。

3.3 缺省適配器
缺省適配器模式是適配器模式的一種變體,其應(yīng)用也較為廣泛。缺省適配器模式的定義如下: 缺省適配器模式(Default Adapter Pattern):當(dāng)不需要實(shí)現(xiàn)一個(gè)接口所提供的所有方法時(shí),可先設(shè)計(jì)一個(gè)抽象類實(shí)現(xiàn)該接口,并為接口中每個(gè)方法提供一個(gè)默認(rèn)實(shí)現(xiàn)(空方法),那么該抽象類的子類可以選擇性地覆蓋父類的某些方法來實(shí)現(xiàn)需求,它適用于不想使用一個(gè)接口中的所有方法的情況,又稱為單接口適配器模式。
缺省適配器模式結(jié)構(gòu)如圖所示:

缺省適配器.jpg

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

  • ServiceInterface(適配者接口):它是一個(gè)接口,通常在該接口中聲明了大量的方法。
  • AbstractServiceClass(缺省適配器類):它是缺省適配器模式的核心類,使用空方法的形式實(shí)現(xiàn)了在 ServiceInterface 接口中聲明的方法。通常將它定義為抽象類,因?yàn)閷?duì)它進(jìn)行實(shí)例化沒有任何意義。
  • ConcreteServiceClass(具體業(yè)務(wù)類):它是缺省適配器類的子類,在沒有引入適配器之前,它需要實(shí)現(xiàn)適配者接口,因此需要實(shí)現(xiàn)在適配者接口中定義的所有方法,而對(duì)于一些無須使用的方法也不得不提供空實(shí)現(xiàn)。在有了缺省適配器之后,可以直接繼承該適配器類,根據(jù)需要有選擇性地覆蓋在適配器類中定義的方法。

4.適配器模式優(yōu)缺點(diǎn)總結(jié)


無論是對(duì)象適配器模式還是類適配器模式都具有如下優(yōu)點(diǎn):

  • 將目標(biāo)類和適配者類解耦,通過引入一個(gè)適配器類來重用現(xiàn)有的適配者類,無須修改原有結(jié)構(gòu)。

  • 增加了類的透明性和復(fù)用性,將具體的業(yè)務(wù)實(shí)現(xiàn)過程封裝在適配者類中,對(duì)于客戶端類而言是透明的,而且提高了適配者的復(fù)用性,同一個(gè)適配者類可以在多個(gè)不同的系統(tǒng)中復(fù)用。

  • 靈活性和擴(kuò)展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎(chǔ)上增加新的適配器類,完全符合“開閉原則”。

類適配器模式還有如下優(yōu)點(diǎn):

  • 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強(qiáng)。

對(duì)象適配器模式還有如下優(yōu)點(diǎn):

  • 一個(gè)對(duì)象適配器可以把多個(gè)不同的適配者適配到同一個(gè)目標(biāo);

  • 可以適配一個(gè)適配者的子類,由于適配器和適配者之間是關(guān)聯(lián)關(guān)系,根據(jù)“里氏代換原則”,適配者的子類也可通過該適配器進(jìn)行適配。


類適配器模式的缺點(diǎn):

  • 對(duì)于 Java、C# 等不支持多重類繼承的語言,一次最多只能適配一個(gè)適配者類,不能同時(shí)適配多個(gè)適配者;

  • 適配者類不能為最終類,如在 Java 中不能為 final 類,C# 中不能為 sealed 類;

  • 在 Java、C# 等語言中,類適配器模式中的目標(biāo)抽象類只能為接口,不能為類,其使用有一定的局限性。

對(duì)象適配器模式的缺點(diǎn):

  • 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個(gè)或多個(gè)方法,可以先做一個(gè)適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當(dāng)做真正的適配者進(jìn)行適配,實(shí)現(xiàn)過程較為復(fù)雜。

適用場(chǎng)景
在以下情況下可以考慮使用適配器模式:

  • 系統(tǒng)需要使用一些現(xiàn)有的類,而這些類的接口(如方法名)不符合系統(tǒng)的需要,甚至沒有這些類的源代碼。
  • 想創(chuàng)建一個(gè)可以重復(fù)使用的類,用于與一些彼此之間沒有太大關(guān)聯(lián)的一些類,包括一些可能在將來引進(jìn)的類一起工作。

博客搬家:大坤的個(gè)人博客
歡迎評(píng)論哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容