C++設計模式之適配器模式(adapter)(結構型)

一、結構型模式概述

    結構型模式(Structural Pattern)描述如何將類或者對象結合在一起形成更大的結構,就像搭積木,可以通過簡單積木的組合形成複雜的、功能更爲強大的結構。

    結構型模式可以分爲類結構型模式對象結構型模式

        • 類結構型模式關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係。

        • 對象結構型模式關心類與對象的組合,通過關聯關係使得在一個類中定義另一個類的實例對象,然後通過該對象調用其方法。根據“合成複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是對象結構型模式。

類適配器和對象適配器區別

類適配器的重點在於類,是通過構造一個繼承Adaptee類來實現適配器的功能;
對象適配器的重點在於對象,是通過在直接包含Adaptee類來實現的,當需要調用特殊功能的時候直接使用Adapter中包含的那個Adaptee對象來調用特殊功能的方法即可。

注意事項:

類適配器使用多重繼承對一個接口與另一個接口進行匹配。對象適配器依賴於對象組合。類適配器和對象適配器有不同的權衡。類適配器:A、用一個具體的Adapter類對Adaptee和Target進行匹配。結果是當我們想要匹配一個類以及所有它的子類時,類Adapter將不能勝任工作;B、使得Adapter可以重新定義Adaptee的部分行爲,因爲Adapter是Adaptee的一個子類;C、僅僅引入了一個對象,並不需要額外的指針以間接得到adaptee。

對象適配器:A、允許一個Adapter與多個Adaptee----即Adaptee本身以及它的所有子類(如果有子類的話)同時工作。Adapter也可以一次給所有的Adaptee添加功能;B、使得重定義Adaptee的行爲比較困難。這就需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身。

(使用Adapter模式時需要考慮的其它一些因素有:A、Adapter的匹配程度:對Adaptee的接口與Target的接口進行匹配的工作量各個Adapter可能不一樣。工作範圍可能是,從簡單的接口轉換到支持完全不同的操作集合。Adapter的工作量取決於Target接口與Adaptee接口的相似程度。B、可插入的Adapter:當其它的類使用一個類時,如果所需的假定條件越少,這個類就更具可複用性。如果將接口匹配構建爲一個類,就不需要假定對其它的類可見的是一個相同的接口。也就是說,接口匹配使得我們可以將自己的類加入到一些現有的系統中去,而這些系統對這個類的接口可能會有所不同。
 

二、模式定義

適配器模式將一個類的接口轉換成客戶希望的另外一個接口,使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。它包括類適配器和對象適配器。

在軟件開發中採用類似於電源適配器的設計和編碼技巧被稱爲適配器模式。

    通常情況下,客戶端可以通過目標類的接口訪問它所提供的服務。有時,現有的類可以滿足客戶類的功能需要,但是它所提供的接口不一定是客戶類所期望的,這可能是因爲現有類中方法名與目標類中定義的方法名不一致等原因所導致的。在這種情況下,現有的接口需要轉化爲客戶類期望的接口,這樣保證了對現有類的重用。如果不進行這樣的轉化,客戶類就不能利用現有類所提供的功能,適配器模式可以完成這樣的轉化。

    在適配器模式中可以定義一個包裝類,包裝不兼容接口的對象,這個包裝類指的就是適配器(Adapter),它所包裝的對象就是適配者(Adaptee),即被適配的類。

    適配器提供客戶類需要的接口,適配器的實現就是把客戶類的請求轉化爲對適配者的相應接口的調用。也就是說:當客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類並不直接訪問適配者類。因此,適配器可以使由於接口不兼容而不能交互的類可以一起工作。這就是適配器模式的模式動機。

 

三、ULM圖

類適配器

 

 對象適配器

適配器模式包含如下角色:

    • Target:目標抽象類

    • Adapter:適配器類

    • Adaptee:適配者類

    • Client:客戶類

在這裏,Target這個類中的接口才是客戶所期望的類。Adapter在適配器內部包裝了一個被是陪對象既Adaptee,把源接口轉換爲目標接口。Adaptee類是需要適配的類,其中的接口並不是Client所期望的接口所以需要被適配。

對象適配器則
• 允許一個Adapter與多個Adaptee—即Adaptee本身以及它的所有子類(如果有子類的話)—同時工作。Adapter也可以一次給所有的Adaptee添加功能。
• 使得重定義Adaptee的行爲比較困難。這就需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身。
使用Adapter模式時需要考慮的其他一些因素有:

1) Adapter的匹配程度 對Adaptee的接口與Target的接口進行匹配的工作量各個Adapter可能不一樣。工作範圍可能是,從簡單的接口轉換(例如改變操作名 )到支持完全不同的操作集合。Adapter的工作量取決於Target接口與Adaptee接口的相似程度
2) 可插入的Adapter   當其他的類使用一個類時,如果所需的假定條件越少,這個類就更具可複用性。如果將接口匹配構建爲一個類,
就不需要假定對其他的類可見的是一個相同的接口。也就是說,接口匹配使得我們可以將自己的類加入到一些現有的系統中去,
而這些系統對這個類的接口可能會有所不同。 
3) 使用雙向適配器提供透明操作 使用適配器的一個潛在問題是,它們不對所有的客戶都透明。被適配的對象不再兼容 Adaptee的接口,
因此並不是所有 Adaptee對象可以被使用的地方它都可以被使用。雙向適配器提供了這樣的透明性。
在兩個不同的客戶需要用不同的方式查看同一個對象時,雙向適配器尤其有用。

四、實例

4.1 STL中queue和stack(對象適配器)

在STL中就用到了適配器模式。STL實現了一種數據結構,稱爲雙端隊列(deque),支持前後兩段的插入與刪除。STL實現棧和隊列時,沒有從頭開始定義它們,而是直接使用雙端隊列實現的。這裏雙端隊列就扮演了適配器的角色。隊列用到了它的後端插入,前端刪除。而棧用到了它的後端插入,後端刪除。假設棧和隊列都是一種順序容器,有兩種操作:壓入和彈出。

#include <iostream>

//雙端隊列(已有實現的類和接口)
class Deque
{
public:
	void push_back(int x) { std::cout << "Deque push_back" << std::endl; }
	void push_front(int x) { std::cout << "Deque push_front" << std::endl; }
	void pop_back() { std::cout << "Deque pop_back" << std::endl; }
	void pop_front() { std::cout << "Deque pop_front" << std::endl; }
};
//順序容器(客戶端期望的類和接口)
class Sequence
{
public:
	virtual void push(int x) = 0;
	virtual void pop() = 0;
};
//棧(adapter)
class Stack : public Sequence
{
public:
	void push(int x) { deque.push_back(x); }
	void pop() { deque.pop_back(); }
private:
	Deque deque; //雙端隊列
};
//隊列(adapter)
class Queue : public Sequence
{
public:
	void push(int x) { deque.push_back(x); }
	void pop() { deque.pop_front(); }
private:
	Deque deque; //雙端隊列
};

int main()
{
	Sequence* s1 = new Stack();//客戶期望的stack
	Sequence* s2 = new Queue();//客戶期望的queue

	s1->push(1); 
	s1->pop();

	s2->push(1); 
	s2->pop();

	delete s1; 
	delete s2;

	return 0;
}

4.2 仿生機器人(類適配器)

現需要設計一個可以模擬各種動物行爲的機器人,在機器人中定義了一系列方法,如機器人叫喊方法cry()、機器人移動方法move()等。如果希望在不修改已有代碼的基礎上使得機器人能夠像狗一樣叫,像狗一樣跑,使用適配器模式進行系統設計。這裏只需要通過cry適配wang,通過move適配run。

#include <iostream>

//target抽象類Robot
class Robot {
public:
	virtual void cry() = 0;
	virtual void move() = 0;
};

//adaptee類Dog
class Dog
{
public:
	void wang() 
	{
		std::cout << "狗汪汪叫!" << std::endl;
	}

	void run() 
	{
		std::cout << "狗快快跑!" << std::endl;
	}
};

//adapter類DogAdapter
class DogAdapter : public Robot, private Dog 
{
public:
	void cry() 
	{
		std::cout << "機器人模仿:" << std::endl;
		Dog::wang();
	}

	void move() 
	{
		std::cout << "機器人模仿:" << std::endl;
		Dog::run();
	}
};

//Client 
int main(void) 
{
	Robot* robot = (Robot*)new DogAdapter();
	robot->cry();
	robot->move();
	return 0;
}

結果如下:

五、模式優缺點

優點

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

    • 增加了類的透明性和複用性,將具體的實現封裝在適配者類中,對於客戶端類來說是透明的,而且提高了適配者的複用性。

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

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

缺點

    • 對於Java、C#等不支持多重繼承的語言,一次最多隻能適配一個適配者類,而且目標抽象類只能爲抽象類,不能爲具體類,其使用有一定的侷限性,不能將一個適配者類和它的子類都適配到目標接口。

六、模式擴展

默認適配器模式(Default Adapter Pattern)或缺省適配器模式

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

雙向適配器

    • 在對象適配器的使用過程中,如果在適配器中同時包含對目標類和適配者類的引用,適配者可以通過它調用目標類中的方法,目標類也可以通過它調用適配者類中的方法,那麼該適配器就是一個雙向適配器。

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