C++設計模式——適配器模式(對象適配器、類適配器)

http://blog.jobbole.com/109381/?winzoom=1


前言 

    現在的筆記本都只存在USB接口,用來口,電腦上存在的是USB接口,兩者如何通信呢?可以使用USB轉串口線連接電腦和嵌入式設備,其中USB一端連接電腦、串口和外部設備進行通信。然而在一些嵌入式設備上(例如ARM9),通常使用串口和電腦進行通信。嵌入式設備上存在的是串一端連接嵌入式設備。本來電腦和嵌入式設備由於接口不兼容,無法進行通信,而使用USB轉串口線這個適配器,兩者之間就可以正常進行數據通信。在設計模式中,也存在一種類似的模式,存在兩個接口不同的類,可以使用一個適配器類來將一個接口轉換爲客戶希望的另一個接口,稱爲適配器模式。

1、適配器模式

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

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

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

    在適配器模式中,我們通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關係的類可以協同工作。根據適配器類與適配者類的關係不同,適配器模式可分爲對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承(或實現)關係。在實際開發中,對象適配器的使用頻率更高。

              對象適配器模式結構圖

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

    Target(目標抽象類):目標抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。

    Adapter(適配器類):適配器可以調用另一個接口,作爲一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它通過繼承Target並關聯一個Adaptee對象使二者產生聯繫。

    Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的接口,這個接口需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的源代碼。

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


2、Socket網絡通信的設計與實現------對象適配器

    使用TCP進行網絡通信,一般都會包含創建套接字、綁定套接字、監聽套接字、連接套接字等4個過程。某網絡公司已經有一套成熟的套接字軟件包,裏面包含網絡通信的4個過程。現欲開發一套新的聊天軟件,決定複用已經成熟的套接字軟件包。

 

    SocketPackage就是成熟的套接字軟件包,也就是Adaptee適配者類,包含創建套接字、綁定套接字、監聽套接字、連接套接字等成熟方法; Socket是目標抽象類Target,定義了客戶希望的方法; SocketAdapter爲適配器類,將成熟軟件包裏面的方法轉爲Socket類中客戶希望的方法。

2.1 待適配的類——成熟的套接字

    成熟的套接字軟件包實現代碼如下:

[cpp] view plain copy

  1. #ifndef _SOCKET_PACKAGE_H_  

  2. #define _SOCKET_PACKAGE_H_  

  3.   

  4. #include <iostream>  

  5. #include <string>  

  6. using namespace std;  

  7.   

  8.   

  9. //套接字包(可以被複用)  

  10. class SocketPackage  

  11. {  

  12. public:  

  13.     void CreateSpecificSocket()  

  14.     {  

  15.         cout << "創建套接字" << endl;  

  16.     }  

  17.   

  18.     void BindSpecificSocket()  

  19.     {  

  20.         cout << "綁定套接字" << endl;  

  21.     }  

  22.   

  23.     void ListenSpecificSocket()  

  24.     {  

  25.         cout << "監聽套接字" << endl;  

  26.     }  

  27.   

  28.     void ConnecSpecifictSocket()  

  29.     {  

  30.         cout << "連接套接字" << endl;  

  31.     }  

  32.   

  33. };  

  34.   

  35. #endif  


 現欲開發的聊天軟件定義了一個抽象類Socket,也就是目標抽象類Target,裏面包含創建套接字、綁定套接字、監聽套接字、連接套接字等客戶希望的方法,但這些方法和成熟軟件包中的方法不同。如:Socket類創建套接字方法爲CreateSocket,而SocketPackage類創建套接字方法爲CreateSpecificSocket。這兩個方法不同,無法進行通信,爲了複用成熟套接字軟件包裏面的方法,可以提供一個適配器類,繼承於抽象的Socket類。在適配器中的方法將調用套接字軟件包裏面的方法。

2.2 適配器類——新的套接字

    套接字適配器類實現代碼如下:

[cpp] view plain copy

  1. #ifndef _SOCKET_H_  

  2. #define _SOCKET_H_  

  3.   

  4. #include "SocketPackage.h"  

  5.   

  6. //抽象套接字類  

  7. class Socket  

  8. {  

  9. public:  

  10.     //創建套接字  

  11.     virtual void CreateSocket() = 0;  

  12.   

  13.     //綁定套接字  

  14.     virtual void BindSocket() = 0;  

  15.   

  16.     //監聽套接字  

  17.     virtual void ListenSocket() = 0;  

  18.   

  19.     //連接套接字  

  20.     virtual void ConnectSocket() = 0;  

  21. };  

  22.   

  23.   

  24.   

  25. //套接字適配器  

  26. class SocketAdapter : public Socket  

  27. {  

  28. private:  

  29.     SocketPackage * m_pSocketPackage;  

  30. public:  

  31.     //構造函數,創建一個需要複用的套接字包對象  

  32.     SocketAdapter()  

  33.     {  

  34.         m_pSocketPackage = new SocketPackage();  

  35.     }  

  36.   

  37.     //銷燬需要複用的套接字包對象  

  38.     ~SocketAdapter()  

  39.     {  

  40.         if( NULL != m_pSocketPackage )  

  41.         {  

  42.             delete m_pSocketPackage;  

  43.             m_pSocketPackage = NULL;  

  44.         }  

  45.     }  

  46.   

  47.     //創建套接字  

  48.     void CreateSocket()  

  49.     {  

  50.         m_pSocketPackage->CreateSpecificSocket();  

  51.     }  

  52.       

  53.     //綁定套接字  

  54.     void BindSocket()  

  55.     {  

  56.         m_pSocketPackage->BindSpecificSocket();  

  57.     }  

  58.       

  59.     //監聽套接字  

  60.     void ListenSocket()  

  61.     {  

  62.         m_pSocketPackage->ListenSpecificSocket();  

  63.     }  

  64.       

  65.     //連接套接字  

  66.     void ConnectSocket()  

  67.     {  

  68.         m_pSocketPackage->ConnecSpecifictSocket();  

  69.     }  

  70. };  

  71.   

  72.   

  73. #endif  

2.3 測試

測試代碼實現如下:

[cpp] view plain copy

  1. #include <iostream>  

  2. #include "Socket.h"  

  3.   

  4.   

  5. using namespace std;  

  6.   

  7.   

  8. int main()  

  9. {  

  10.     //創建套接字適配器對象  

  11.     Socket * pSocketAdapter = new SocketAdapter();  

  12.       

  13.     //使用適配器進行套接字操作  

  14.     pSocketAdapter->CreateSocket();  

  15.     pSocketAdapter->BindSocket();  

  16.     pSocketAdapter->ListenSocket();  

  17.     pSocketAdapter->ConnectSocket();  

  18.   

  19.   

  20.     //銷燬操作  

  21.     delete pSocketAdapter;  

  22.     pSocketAdapter = NULL;  

  23.       

  24.     return 0;  

  25. }  


編譯並執行,結果如下:


    SocketAdapter套接字適配器類和SocketPackage套接字包是一種組合的關係,當調用套接字適配器類中的相應方法時,將調用套接字包對應的方法,通過組合的方式,實現對套接字軟件包模塊的複用。客戶端不需要直接操作這個已經成熟的套接字軟件包,而是由套接字適配器類進行委託操作,降低了客戶端和適配者類的耦合。

    對象適配器模式中,適配器類Adapter和適配者類Adatpee類是一種關聯關係,或者組合關係。適配器類維護一個適配者類的引用,在適配器的方法中調用相應的適配者類中的方法,實現對適配者功能的複用,這個過程對客戶端是透明的,客戶類不直接訪問適配者類。

3、Socket網絡通信的設計與實現------類適配器

除了對象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對象適配器模式最大的區別在於適配器和適配者之間的關係不同,對象適配器模式中適配器和適配者之間是關聯關係,而類適配器模式中適配器和適配者是繼承關係。不管是對象適配器還是類適配器,都是爲了複用適配者類的功能。

    SocketPackage就是成熟的套接字軟件包,也就是Adaptee適配者類,包含創建套接字、綁定套接字、監聽套接字、連接套接字等成熟方法; Socket是目標抽象類Target,定義了客戶希望的方法; SocketAdapter爲適配器類,繼承自Socket類和SocketPackage類,將成熟軟件包裏面的方法轉爲Socket類中客戶希望的方法。

3.1 帶適配類——成熟套接字

    成熟的套接字軟件包實現代碼如下:

[cpp] view plain copy

  1. #ifndef _SOCKET_PACKAGE_H_  

  2. #define _SOCKET_PACKAGE_H_  

  3.   

  4. #include <iostream>  

  5. #include <string>  

  6. using namespace std;  

  7.   

  8.   

  9. //套接字包(可以被複用)  

  10. class SocketPackage  

  11. {  

  12. public:  

  13.     void CreateSpecificSocket()  

  14.     {  

  15.         cout << "創建套接字" << endl;  

  16.     }  

  17.   

  18.     void BindSpecificSocket()  

  19.     {  

  20.         cout << "綁定套接字" << endl;  

  21.     }  

  22.   

  23.     void ListenSpecificSocket()  

  24.     {  

  25.         cout << "監聽套接字" << endl;  

  26.     }  

  27.   

  28.     void ConnecSpecifictSocket()  

  29.     {  

  30.         cout << "連接套接字" << endl;  

  31.     }  

  32.   

  33. };  

  34.   

  35. #endif  


SocketAdapter套接字適配器類和SocketPackage套接字包類不在是組合的關係,SocketAdapter繼承於Socket類和SocketPackage,當調用SocketAdapter套接字適配器類中的方法,將會調用其基類SocketPackage的相應方法,通過繼承的方式實現對適配者類的功能複用。

3.2 適配類——採用(多)繼承

    套接字適配器類實現代碼如下:

[cpp] view plain copy

  1. #ifndef _SOCKET_H_  

  2. #define _SOCKET_H_  

  3.   

  4. #include "SocketPackage.h"  

  5.   

  6. //抽象套接字類  

  7. class Socket  

  8. {  

  9. public:  

  10.     //創建套接字  

  11.     virtual void CreateSocket() = 0;  

  12.   

  13.     //綁定套接字  

  14.     virtual void BindSocket() = 0;  

  15.   

  16.     //監聽套接字  

  17.     virtual void ListenSocket() = 0;  

  18.   

  19.     //連接套接字  

  20.     virtual void ConnectSocket() = 0;  

  21. };  

  22.   

  23.   

  24.   

  25. //套接字適配器(類適配器)  

  26. class SocketAdapter : public Socket, SocketPackage  

  27. {  

  28. public:  

  29.   

  30.     //創建套接字  

  31.     void CreateSocket()  

  32.     {  

  33.         CreateSpecificSocket();  

  34.     }  

  35.       

  36.     //綁定套接字  

  37.     void BindSocket()  

  38.     {  

  39.         BindSpecificSocket();  

  40.     }  

  41.       

  42.     //監聽套接字  

  43.     void ListenSocket()  

  44.     {  

  45.         ListenSpecificSocket();  

  46.     }  

  47.       

  48.     //連接套接字  

  49.     void ConnectSocket()  

  50.     {  

  51.         ConnecSpecifictSocket();  

  52.     }  

  53. };  

  54.   

  55.   

  56. #endif  

3.3 測試

測試程序實現代碼如下:

[cpp] view plain copy

  1. #include <iostream>  

  2. #include "Socket.h"  

  3.   

  4. using namespace std;  

  5.   

  6. int main()  

  7. {  

  8.     //創建套接字適配器對象  

  9.     Socket * pSocketAdapter = new SocketAdapter();  

  10.       

  11.     //使用適配器進行套接字操作  

  12.     pSocketAdapter->CreateSocket();  

  13.     pSocketAdapter->BindSocket();  

  14.     pSocketAdapter->ListenSocket();  

  15.     pSocketAdapter->ConnectSocket();  

  16.   

  17.     //銷燬操作  

  18.     delete pSocketAdapter;  

  19.     pSocketAdapter = NULL;  

  20.       

  21.     return 0;  

  22. }  


編譯並執行,程序結果如下:


    適配器類繼承於目標抽象類和適配者類,當調用適配器類的方法時,在該方法內部將調用其基類,也就是適配者類中相應的方法,通過繼承的方式,實現複用適配者類的功能。

    不管是對象適配器還是類適配器,都是爲了實現功能的複用。

4、適配器模式總結

    在對象適配器模式中,適配器與適配者之間是關聯關係;在類適配器模式中,適配器與適配者之間是繼承關係。不論是對象適配器還是類適配器,適配器模式都將現有接口轉化爲客戶類所期望的接口,實現了對現有類的複用。爲了避免兩個或者多個事物之間直接耦合,應該如何分配職責? 大多數情況都可以通過增加一層間接性來解決,將職責分配給中介對象,使其作爲其他構件或服務之間的媒介,以避免他們之間的直接耦合。在適配器模式中,爲了避免客戶類和與適配者類直接耦合,也就是客戶類直接訪問適配者類,可以增加一個適配器類,降低客戶類與適配者類的耦合性。當客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。它是一種使用頻率非常高的設計模式,在軟件開發中得以廣泛應用。

4.1 主要優點

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

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

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

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

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

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

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

    (1) 一個對象適配器可以把多個不同的適配者適配到同一個目標

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

4.2 主要缺點

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

    (1) 對於Java、C#等不支持多重類繼承的語言,一次最多隻能適配一個適配者類,不能同時適配多個適配者;

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

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

4.3 適配器模式具體應用

    (1)複用已經寫好的功能模塊,例如:已經寫好了一個員工信息模塊,要重新開發一個新的企業管理系統,可以複用員工信息模塊。

    (2)程序運行時的日誌信息,可以記錄到數據庫,也可以記錄到Txt文件中。現在變更需求,使日誌信息能夠同時記錄到數據庫和Txt文件中。可以添加一個適配器類,適配數據庫操作和文件操作。

    (3)在軟件開發過程中,我們會開發出一些功能性模塊。如排序算法模塊、查找模塊、或者封裝鏈表、隊列、樹等操作。在需要複用這些功能的時候,直接使用,無需重新開發這些功能模塊。

    (4)網上銀行加密模塊開發: 已經開發好了一套網銀加密模塊,現在需要重新開發一個類似的軟件,可以直接複用已經開發好的加密模塊。

    (5)某公司已經開發了一套火車票識別軟件,包含二值化、去噪、版面分析、欄目提取等模塊。現在要開發銀行卡識別項目,則可以直接複用火車票識別中的功能模塊。

    (6)某公司想要開發一套輸入法軟件,可以購買搜狗輸入法的搜索模塊,直接加以複用,無需自己重頭開發。

    (7)生活中的適配器: 電源適配器,USB轉串口線,水管專修工人使用各種接口連接本來沒法進行相連的管道。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章