【設計模式】 模式PK:裝飾模式VS適配器模式
1、概述
裝飾模式和適配器模式在通用類圖上沒有太多的相似點,差別比較大,但是它們的功能有相似的地方:都是包裝作用,都是通過委托方式實現其功能。不同點是:裝飾模式包裝的是自己的兄弟類,隸屬於同一個家族(相同接口或父類),適配器模式則修飾非血緣關系類,把一個非本家族的對象偽裝成本家族的對象,註意是偽裝,因此它的本質還是非相同接口的對象。
大家都應該聽過醜小鴨的故事吧,我們今天就用這兩種模式分別講述醜小鴨的故事。話說鴨媽媽有5個孩子,其中4個孩子都是黃白相間的顏色,而最小的那只也就是叫做醜小鴨的那只,是純白色的,與兄弟姐妹不相同,在遭受了諸多的嘲諷和譏笑後,最終醜小鴨變成了一只美麗的天鵝。那我們如何用兩種不同模式來描述這一故事呢?
2、裝飾模式描述醜小鴨
2.1 類圖
用裝飾模式來描述醜小鴨,首先就要肯定醜小鴨是一只天鵝,只是因為她小或者是鴨媽媽的無知才沒有被認出是天鵝,經過一段時間後,它逐步變成一個漂亮、自信、優美的白天鵝。根據分析我們可以這樣設計,先設計一個醜小鴨,然後根據時間先後來進行不同的美化處理,怎麽美化呢?先長出漂亮的羽毛,然後逐步展現出異於鴨子的不同行為,如飛行,最終在具備了所有的行為後,它就成為一只純粹的白天鵝了。類圖比較簡單,非常標準的裝飾模式。
2.2 代碼
2.2.1 天鵝接口
我們按照故事的情節發展一步一步地實現程序。初期的時候,醜小鴨表現得很另類,叫聲不同,外形不同,致使周圍的親戚、朋友都對她鄙視,那我們來建立這個過程,由於醜小鴨的本質就是一個天鵝,我們就先生成一個天鵝的接口。
class CISwan { public: CISwan(){}; ~CISwan(){}; //天鵝會飛 virtual void mvFly() = 0; //天鵝會叫 virtual void mvCry() = 0; //天鵝都有漂亮的外表 virtual void mvDesAppaearance() = 0; };
2.2.2 醜小鴨
我們定義了天鵝的行為,都會飛行、會叫,並且可以描述她們漂亮的外表。醜小鴨是一只白天鵝,是"is-a"的關系,也就是需要實現這個接口了。
class CUglyDuckling : publicCISwan { public: CUglyDuckling(){}; ~CUglyDuckling(){}; //醜小鴨的叫聲 void mvCry() { cout << "叫聲是克嚕——克嚕——克嚕" << endl; }; //醜小鴨的外形 void mvDesAppaearance() { cout << "外形是臟兮兮的白色, 毛茸茸的大腦袋" << endl; } //醜小鴨還比較小,不能飛 void mvFly(){ cout << "不能飛行" << endl; }; };
2.2.3 抽象裝飾類
醜小鴨具備了天鵝的所有行為和屬性,因為她本來就是一只白天鵝,只是因為她太小了還不能飛行,也不能照顧自己,所以醜醜的,在經過長時間的流浪生活後,醜小鴨長大了。終於有一天,她發現自己竟然變成了一只美麗的白天鵝,有著漂亮、潔白的羽毛,而且還可以飛行,這完全是一種升華行為。我們來看看她的行為(飛行)和屬性(外形)是如何加強的。
class CDecorator : public CISwan { public: CDecorator(CISwan *opSwan){ mopSwan = opSwan; }; ~CDecorator(){}; virtual void mvCry() { mopSwan->mvCry(); } virtual void mvFly() { mopSwan->mvFly(); } virtual void mvDesAppaearance() { mopSwan->mvDesAppaearance(); } private: CISwan *mopSwan; };
2.2.4 外形裝飾
這是一個非常簡單的代理模式。我們再來看醜小鴨是如何開始變得美麗的,變化是由外及裏的,有了漂亮的外表才有內心的實質變化。
class CBeautifyAppearance : public CDecorator { public: CBeautifyAppearance(CISwan *opSwan) : CDecorator(opSwan) {}; // 外表美化處理 void mvDesAppaearance() override { cout << "外表是純白色的,非常惹人喜愛!" << endl; }; };
2.2.5 行為裝飾
醜小鴨最後發現自己還能飛行,這是一個行為突破,是對原有行為“不會飛行”的一種強化。
class CStrongBehavior : public CDecorator { public: CStrongBehavior(CISwan *opSwan) : CDecorator(opSwan) {}; ~CStrongBehavior(){}; // 會飛行了 void mvFly() override { cout << "會飛行了!" << endl; }; };
2.2.6 場景調用
所有的故事元素我們都具備了,就等有人來講故事了。
int main() { //很久很久以前, 這裏有一個醜陋的小鴨子 cout << "===很久很久以前, 這裏有一只醜陋的小鴨子===" << endl; CISwan *op_duck = new CUglyDuckling; //展示一下小鴨子 op_duck->mvDesAppaearance(); op_duck->mvCry(); op_duck->mvFly(); cout << "===小鴨子終於發現自己是一只天鵝====" << endl; //首先外形變化了 op_duck = new CBeautifyAppearance(op_duck); //其次行為也發生了改變 op_duck = new CStrongBehavior(op_duck); //雖然還是叫醜小鴨, 但是已經發生了很大變化 op_duck->mvDesAppaearance(); op_duck->mvCry(); op_duck->mvFly(); return 0; }
2.2.7 執行結果
使用裝飾模式描述醜小鴨蛻變的過程是如此簡單,它關註了對象功能的強化,是對原始對象的行為和屬性的修正和加強,把原本被人歧視、冷落的醜小鴨通過兩次強化處理最終轉變為受人喜愛、羨慕的白天鵝。
3、適配器模式實現醜小鴨
3.1 類圖
采用適配器模式實現醜小鴨變成白天鵝的過程要從鴨媽媽的角度來分析,鴨媽媽有5個孩子,它認為這5個孩子都是她的後代,都是鴨類,但是實際上是有一只(也就是醜小鴨)不是真正的鴨類,她是一只小白天鵝,因為太小,差別太細微,很難分辨,導致鴨媽媽認為她是一只鴨子,從鴨子的審美觀來看,醜小鴨是醜陋的。通過分析,我們要做的就是要設計兩個對象:鴨和天鵝,然後鴨媽媽把一只天鵝看成了小鴨子,最終時間到來的時候醜小鴨變成了白天鵝。
類圖非常簡單,我們定義了兩個接口:鴨類接口和天鵝類接口,然後建立了一個適配器UglyDuckling,把一只白天鵝封裝成了小鴨子。
3.2 代碼
3.2.1 鴨類接口
class CIDuck { public: CIDuck(){}; ~CIDuck(){}; //會叫 virtual void mvCry() = 0; //鴨子的外形 virtual void mvDesAppearance() = 0; //描述鴨子的其他行為 virtual void mvDesBehavior() = 0; };
鴨類有3個行為,一個是鴨會叫,一個是外形描述,還有一個是綜合性的其他行為描述,例如會遊泳等。
3.2.2 小鴨子
class CDuckling : public CIDuck { public: CDuckling(){}; ~CDuckling(){}; void mvCry() { cout << "叫聲是嘎——嘎——嘎" << endl; }; void mvDesAppearance() { cout << "外形是黃白相間,嘴長" << endl; }; // 鴨子的其他行為, 如遊泳 void mvDesBehavior() { cout << "會遊泳" << endl; }; };
3.2.3 白天鵝
4只正宗的小鴨子形象已經清晰地定義出來了。鴨媽媽還有一個孩子,就是另類的醜小鴨,她實際是一只白天鵝。我們先定義出白天鵝。
class CISwan { public: CISwan(){}; ~CISwan(){}; //天鵝會飛 virtual void mvFly() = 0; //天鵝會叫 virtual void mvCry() = 0; //天鵝都有漂亮的外表 virtual void mvDesAppaearance() = 0; }; class CWhiteSwan : public CISwan { public: CWhiteSwan(){}; ~CWhiteSwan(){}; //白天鵝的叫聲 void mvCry() { cout << "叫聲是克嚕——克嚕——克嚕" << endl; }; //白天鵝的外形 void mvDesAppaearance() { cout << "外形是純白色,惹人喜愛" << endl; } //天鵝是能夠飛行的 void mvFly(){ cout << "能夠飛行" << endl; }; };
3.2.4 當成鴨子的白天鵝
但是,鴨媽媽卻不認為自己這個另類的孩子是白天鵝,它從自己的觀點出發,認為她很醜陋,有礙自己的臉面,於是驅趕她——鴨媽媽把這只小天鵝誤認為一只鴨。
class CUglyDuckling : public CWhiteSwan, public CIDuck { public: CUglyDuckling(){}; ~CUglyDuckling(){}; void mvCry() { CWhiteSwan::mvCry(); }; void mvDesAppearance() { CWhiteSwan::mvDesAppaearance(); }; void mvDesBehavior() { cout << "會遊泳!" << endl; CWhiteSwan::mvFly(); } };
3.2.5 調用
天鵝被看成了鴨子,有點暴殄天物的感覺。我們再來創建一個場景類來描述這一場景。
int main() { //鴨媽媽有5個孩子, 其中4個都是一個模樣 cout << "===媽媽有五個孩子, 其中四個模樣是這樣的: ===" << endl; CIDuck *op_duck = new CDuckling(); op_duck->mvCry(); op_duck->mvDesAppearance(); op_duck->mvDesBehavior(); cout << "===一只獨特的小鴨子, 模樣是這樣的: ===" << endl; CIDuck *op_ugly_duck = new CUglyDuckling; op_ugly_duck->mvCry(); op_ugly_duck->mvDesAppearance(); op_ugly_duck->mvDesBehavior(); return 0; }
3.2.6 執行結果
小天鵝被認為是一只醜陋的小鴨子...采用適配器模式講述醜小鴨的故事,我們首先觀察到的是鴨與天鵝的不同點,建立了不同的接口以實現不同的物種,然後在需要的時候(根據故事情節)把一個物種偽裝成另外一個物種,實現不同物種的相同處理過程,這就是適配器模式的設計意圖。
4、總結
我們用兩個模式實現了醜小鴨的美麗蛻變。我們發現:這兩個模式有較多的不同點。
● 意圖不同
裝飾模式的意圖是加強對象的功能,例子中就是把一個怯弱的小天鵝強化成了一個美麗、自信的白天鵝,它不改變類的行為和屬性,只是增加(當然了,減弱類的功能也是可能存在的)功能,使美麗更加美麗,強壯更加強壯,安全更加安全;而適配器模式關註的則是轉化,它的主要意圖是兩個不同對象之間的轉化,它可以把一個天鵝轉化為一個小鴨子看待,也可以把一只小鴨子看成是一只天鵝,它關註轉換。
● 施與對象不同
裝飾模式裝飾的對象必須是自己的同宗,也就是相同的接口或父類,只要在具有相同的屬性和行為的情況下,才能比較行為是增加還是減弱;適配器模式則必須是兩個不同的對象,因為它著重於轉換,只有兩個不同的對象才有轉換的必要,如果是相同對象還轉換什麽?!
● 場景不同
裝飾模式在任何時候都可以使用,只要是想增強類的功能,而適配器模式則是一個補救模式,一般出現在系統成熟或已經構建完畢的項目中,作為一個緊急處理手段采用。
● 擴展性不同
裝飾模式很容易擴展!今天不用這個修飾,好,去掉;明天想再使用,好,加上。這都沒有問題。而且裝飾類可以繼續擴展下去;但是適配器模式就不同了,它在兩個不同對象之間架起了一座溝通的橋梁,建立容易,去掉就比較困難了,需要從系統整體考慮是否能夠撤銷。
【設計模式】 模式PK:裝飾模式VS適配器模式