結構型設計模式(一) --適配器模式

前言:設計模式之結構型模式
軟件模式與具體的應用領域無關,也就是說無論你從事的是移動應用開發、桌面應用開發、Web 應用開發還是嵌入式軟件的開發,都可以使用軟件模式。
在軟件模式中,設計模式是研究最為深入的分支,設計模式用于在特定的條件下為一些重復出現的軟件設計問題提供合理的、有效的解決方案,它融合了眾多專家的設計經驗,已經在成千上萬的軟件中得以應用。1995 年,GoF 將收集和整理好的 23 種設計模式匯編成《設計模式:可復用面向對象軟件的基礎》一書,該書的出版也標志著設計模式正式成為面向對象(Object Oriented)軟件工程的一個重要研究分支。

結構型設計模式統共七種:適配器模式、橋接模式、組合模式、裝飾模式、外觀模式、享元模式和代理模式
今天來記錄適配器模式

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

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


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

簡單來說:
在軟件開發中,有時也存在類似這種不兼容的情況,我們也可以像引入一個電源適配器一樣引入一個稱之為適配器的角色來協調這些存在不兼容的結構,這種設計方案即為適配器模式。

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

問題示意圖

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

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


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

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

適配器模式可以將一個類的接口和另一個類的接口匹配起來,而無須修改原來的適配者接口和抽象目標類接口。適配器模式定義如下:
適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。
適配器模式類圖表示:
在適配器模式中,我們通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關系的類可以協同工作。根據適配器類與適配者類的關系不同,適配器模式可分為對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關系;在類適配器模式中,適配器與適配者之間是繼承(或實現)關系。在實際開發中,對象適配器的使用頻率更高,對象適配器模式結構如圖所示:

對象適配器類圖.png

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

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

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

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

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

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

在圖中,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[]) {
         //調用適配者類QuickSort的排序方  法
         return sortObj.quickSort(array); 
     }
     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 {
     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。

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

3.適配器模式分類


3.1 對象適配器
上文中記錄的便是對象適配器,可以參照上問來進行強化記憶。
3.2 類適配器
除了對象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對象適配器模式最大的區別在于適配器和適配者之間的關系不同,對象適配器模式中適配器和適配者之間是關聯關系,而類適配器模式中適配器和適配者是繼承關系,類適配器模式結構如圖所示:

類適配器結構圖.jpg

根據類適配器模式結構圖,適配器類實現了抽象目標類接口 Target,并繼承了適配者類,在適配器類的 request() 方法中調用所繼承的適配者類的 specificRequest() 方法,實現了適配。
典型的類適配器代碼如下所示:

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

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

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

缺省適配器.jpg

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

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

4.適配器模式優缺點總結


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

  • 將目標類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,無須修改原有結構。

  • 增加了類的透明性和復用性,將具體的業務實現過程封裝在適配者類中,對于客戶端類而言是透明的,而且提高了適配者的復用性,同一個適配者類可以在多個不同的系統中復用。

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

類適配器模式還有如下優點:

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

對象適配器模式還有如下優點:

  • 一個對象適配器可以把多個不同的適配者適配到同一個目標;

  • 可以適配一個適配者的子類,由于適配器和適配者之間是關聯關系,根據“里氏代換原則”,適配者的子類也可通過該適配器進行適配。


類適配器模式的缺點:

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

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

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

對象適配器模式的缺點:

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

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

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

博客搬家:大坤的個人博客
歡迎評論哦~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容