基於東北F4的設計模式情景劇——第一幕 裝飾模式(Decorator Pattern)

第一場 難題未解ios

佈景:鐵嶺,晴天,午後,風。在一幢還算氣派的寫字樓的三層外牆上,掛着一條紅色橫幅,上面用歪歪扭扭的毛筆字寫着「東北F4軟件外包工做室」。大風中,那早已褪色的條幅劇烈地抖動着,發出陣陣嘶吼。房間內,東北F4正在爲大鵬科技股份有限公司開發一款「大俠」遊戲。程序員

劉能(坐在椅子上,扭頭衝衆人):好……好啦,搞……搞定!我建立了一個大俠虛基類,而後派生出了三峽和張鳳霞兩……兩個實例,每一個大俠有兩個功能:攻擊和隱……隱身。這是我畫的那什麼U……U什麼圖:設計模式

劉能:還有,這是我寫的代……代碼:函數

 1 class DaXia   //虛基類  
 2 {  
 3 public:  
 4     DaXia() {};  
 5     virtual ~DaXia() {};  
 6     virtual void GongJi() = 0;  
 7     virtual void YinShen() = 0;  
 8 };  
 9   
10 class SanXia :public DaXia   //三峽版本  
11 {  
12 public:  
13     SanXia() {};  
14     virtual ~SanXia() {};  
15     virtual void GongJi()  
16     {  
17         cout << "來自三峽的攻擊!" << '\n';  
18     }  
19     virtual void YinShen()  
20     {  
21         cout << "三峽已隱身!" << '\n';  
22     }  
23 };  
24   
25 class ZhangFengXia :public DaXia   //張鳳霞版本  
26 {  
27 public:  
28     ZhangFengXia() {};  
29     virtual ~ZhangFengXia() {};  
30     virtual void GongJi()  
31     {  
32         cout << "來自張鳳霞的攻擊!" << '\n';  
33     }  
34     virtual void YinShen()  
35     {  
36         cout << "張鳳霞已隱身!" << '\n';  
37     }  
38 };

(衆人圍攏過來看)學習

宋小寶:那啥,剛纔大鵬給我打電話了,說每一個大俠除了攻擊和隱身兩個核心功能外,還可能會有一些小的輔助功能,好比在攻擊以前先跳一下,或者在隱身以前先喊話。之因此說可能,是由於對特定的大俠而言,可能徹底具有這些輔助功能,也可能只具有一部分,還可能一點都不具有。總之,就是不肯定,大家看,這咋整?this

趙四(一臉得意):有什麼很差整的,太簡單了,讓我來!編碼

(兩分鐘後……)spa

趙四(愈發得意,將一張紙拍在衆人面前):看吧!簡直完美!設計

趙四(搖頭晃屁股):我設計的新類聚合了DaXia類,每一個新類實現本身的動做,而原有的動做複用所聚合的類的代碼。因爲聚合的是大俠類的基類DaXia,因此全部的具體大俠類均可以被新類聚合,即新類適用於每個具體大俠類,這在必定程度上減小了新增類的數量。假設咱們須要一個在攻擊前跳躍的三峽,只要用TiaoDaXia類的實例聚合一個SanXia類的實例就能夠了;再好比,假設咱們須要一個在攻擊前跳躍,在隱身前喊話的張鳳霞,只要用一個TiaoShuoDaXia類的實例聚合一個ZhangFengXia類的實例就能夠了。大家說,個人設計是否是很棒……3d

小瀋陽(沒等趙四說完,就一巴掌扇到他的左臉上):棒你個腦殼!你設計的是啥玩意!你設計的新張鳳霞類根本就不是大俠類的子類,這在邏輯上自己就不合理!更重要的,你看看我客戶端的接口,是這樣式兒的:

void DaXiaDongZuo(DaXia *daxia)  
{  
        daxia->GongJi();  
        daxia->YinShen();  
}

看着沒?你說你設計的破玩意,叫我怎麼用?我得改代碼,得改,知道不!要是明兒需求有了新的變化,照你那麼設計,我還得改,還得改,知道不!(伸手指着趙四)你軟件開發是體育老師教的啊!知道啥叫「開閉原則」不?面向擴展開放,面向修改關閉!

趙四(一手捂着臉,一手指着小瀋陽,憤怒而膽怯):你幹嗎打我!很差就很差吧,你憑什麼打我!(做發狠狀)小樣,這事沒完,沒完,知道不!今天這事必須有個告終!有種你給我等着,你給我十分鐘,十分鐘後,我保證——保證給你一個滿意的方案!

(十分鐘後)

趙四(戰戰兢兢走到小瀋陽面前,顫抖着把一張紙拍在桌子上,立刻又日後跳了一大截):小樣,看好了!這回你滿意了吧!這回你服了吧!你說,服不服!

(衆人誰都沒有理會趙四,全湊過去看趙四的新方案)

趙四(神氣活現地):這回服了吧!我採用派生子類的方式,完美地解決了你剛纔說的全部問題。怎麼樣,我是否是很機智……

劉能(還沒等趙四說完,一巴掌扇在他的右臉上):機智你個腦殼!他的問題解決了,個人呢?如今須要一個既跳又說的張鳳霞,我就得新派生一個類,接下來,只跳不說的一個,只說不跳的一個,這就是三個;三峽下邊,又是三個,這就六個了。若是明天還有新的需求出現,我還不得累吐血了啊!你來編碼,你試試!這麼多類,你叫我怎麼維護?再說,你這是最簡單,也是最笨、最愚蠢的作法!你發現了嗎,對於跳這個功能,你們都是同樣的,而你的設計,卻不得不每一個類都寫一遍!一點複用都沒有!

趙四(哭喪着臉走向宋小寶):寶啊,他們都欺負我,你說,要擴展一個類的方法,不就是聚合和繼承嗎?可他們……嗚……寶啊……(做擁抱狀)

宋小寶(一把推開趙四,極其輕蔑地)瞧你那損色!淨想些找抽的方法!該!

趙四(委屈地):沒想到連你也欺負我!(不服地)你還說我呢,有本事你想一個好辦法!

宋小寶(心虛地):我要是有……有辦法,還輪得着你捱打嗎?

小瀋陽(不耐煩地):行了行了,到下班時間了,今天先這樣吧。你們各回各家,各找各媽。回去都想一想,看這事咋整。

衆人(無奈地):好吧,也只有如此了。

(衆人下,傳來趙四的嘟囔聲:大家都給我聽好了,這事沒完,個人打不會白挨,明兒,我必定叫大家服服帖帖的……)

第二場 趙四逆襲

佈景:打了一晚上麻將的宋小寶揉着惺忪的睡眼晃晃悠悠走進工做室,見其餘三我的正圍在一塊兒說着什麼……

宋小寶(疑惑地):咋地啦,啥事呀?

趙四(神氣活現、搖頭晃腦,提着一張紙走到宋小寶面前):寶啊,看着沒?哥夜以繼日、通宵達旦、徹夜未眠、冥思苦想、絞盡腦汁、搜腸刮肚,終於在黎明的曙光中想出了完美的解決方案!怎麼樣,服不服?

宋小寶(仰起頭髮出一陣魔性的笑聲):哈哈哈哈哈哈……這一大早上班,就聽到這麼可笑的笑話,(轉向趙四,輕蔑地)瞧你那損色!你要是把這事擺平了,我就請大夥吃飯……

劉能、小瀋陽(揮拳做勝利狀):耶!

宋小寶(瞬間蒙圈):不是……咋……咋地?(疑惑地望着劉能和小瀋陽,指着趙四)他真想出來了?

劉能、小瀋陽(衝宋小寶確定地點頭):這回是真的!
宋小寶(心虛而疑惑地):等……等會兒,讓我好好看看。

(衆人的目光又彙集到趙四的設計上)

(好幾分鐘後)

宋小寶(疑惑地望着趙四):你這設計的什麼玩意啊,跟天書似得!

趙四(撇嘴):瞧你那損色!這都看不懂!

宋小寶(厲聲):別搶我臺詞!有本事,你給解釋一下。

(趙四用期待的目光望向劉能和小瀋陽)

劉能(僞裝沒看見,催促道):行啦,趕忙說吧,寶兒都等不及了。

小瀋陽:你們鼓掌!

(衆人鼓掌)

趙四(故做姿態地清了清嗓子):嗯嗯!咱們看,在咱們設計的遊戲中,攻擊和隱身是主要的、核心的功能,而攻擊前跳躍、隱身前喊話是依託核心功能而存在的、是對核心功能的擴展和補充。能夠說,跳躍和喊話是對核心功能的一種裝飾。

衆人(點頭):有道理!

趙四(大模大樣地徘徊在衆人中間):因此,我從大俠類派生出一個裝飾大俠(ZhuangShiDaXia)類,專門處理通過裝飾後的大俠的動做。

劉能(舉手打斷):等一下!這不是和昨天同樣嗎?還得寫好多好多類。

趙四(伸出右手食指在衆人面前搖晃,同時緩慢地搖頭):No、No、No,非也非也。你們仔細看,我把每種裝飾都做爲ZhuangShiDaXia類的子類。例如,在咱們的問題中,有跳躍和喊話兩種裝飾,因而我從ZhuangShiDaXia類派生出跳大俠(TiaoDaXia)和說大俠(ShuoDaXia)兩個子類,咱們不妨稱之爲具體裝飾類。每一個具體裝飾類都只實現本身的裝飾功能,而其它功能則採用daxia指向的對象的版本。以TiaoDaXia爲例,在它的GongJi功能中,先調用本身的Tiao功能,而具體的GongJi功能和該裝飾無關的核心功能,如YinShen功能,則調用daxia指向的對象的版本。

劉能(不解地):daxia又是個什麼鬼?

趙四(神祕而莊重地):而後就是重點了。不知你們是否注意到,ZhuangShiDaXia類除了派生自DaXia類,還聚合了DaXia類,即ZhuangShiDaXia類中有一個DaXia*類型的指針daxia。再回過頭來看,因爲ZhuangShiDaXia類派生自DaXia類,因此,daxia能夠指向上圖中的全部類,包括ZhuangShiDaXia類的子類。

小瀋陽(一臉迷惑):這很重要嗎?

趙四(激動地):過重要了!以ZhuangShiDaXia類的子類TiaoDaXia類爲例,很顯然,它繼承了ZhuangShiDaXia類的daxia指針,而這個指針,還能夠指向ZhuangShiDaXia類的子類,好比ShuoDaXia類,而ShuoDaXia類的daxia指針又能夠指向其它DaXia類的子類……這樣,就能夠實現類的「層次嵌套」,也能夠理解爲一種遞歸,因而,就能夠實現功能的自由組合和調用時的「委託」。換句話說,你想要什麼樣的大俠,就能夠組合出什麼樣的大俠,簡直是變化萬千,無窮無盡!哎呀,不能再說了,再說下去,我都佩服死本身了,哈哈哈哈!

宋小寶(迷惑地):好像明白了,又好像不明白,能舉個例子嗎?

趙四(成竹在胸地):固然能啦,我已經寫好了一個Demo,你們請上眼!

  1 #include<iostream>
  2 
  3 using namespace std;
  4 
  5 class DaXia   //抽象基類
  6 {
  7 public:
  8     DaXia() {};
  9     virtual ~DaXia() 
 10     {};
 11     virtual void GongJi() = 0;
 12     virtual void YinShen() = 0;
 13 };
 14 
 15 class SanXia :public DaXia   //三峽類
 16 {
 17 public:
 18     SanXia() {};
 19     virtual ~SanXia() 
 20     {};
 21     virtual void GongJi()
 22     {
 23         cout << "來自三峽的攻擊!" << '\n';
 24     }
 25     virtual void YinShen()
 26     {
 27         cout << "三峽已隱身!" << '\n';
 28     }
 29 };
 30 
 31 class ZhangFengXia :public DaXia   //張鳳霞類
 32 {
 33 public:
 34     ZhangFengXia() {};
 35     virtual ~ZhangFengXia() 
 36     {};
 37     virtual void GongJi()
 38     {
 39         cout << "來自張鳳霞的攻擊!" << '\n';
 40     }
 41     virtual void YinShen()
 42     {
 43         cout << "張鳳霞已隱身!" << '\n';
 44     }
 45 };
 46 
 47 class ZhuangShiDaXia :public DaXia   //裝飾大俠類
 48 {
 49 protected:
 50     DaXia *daxia;   //聚合DaXia類的直接或間接子類
 51 public:
 52     ZhuangShiDaXia() {};
 53     ZhuangShiDaXia(DaXia *dx)
 54         :daxia(dx)
 55     {}
 56     virtual ~ZhuangShiDaXia()
 57     {
 58         delete daxia;
 59         daxia = NULL;
 60     }
 61     virtual void GongJi()   //由所聚合的類實現功能
 62     {
 63         daxia->GongJi();
 64     }
 65     virtual void YinShen()
 66     {
 67         daxia->YinShen();
 68     }
 69 };
 70 
 71 class TiaoDaXia :public ZhuangShiDaXia   //具備跳裝飾的大俠類
 72 {
 73 protected:
 74     virtual void Tiao()   //實現跳裝飾
 75     {
 76         cout << "跳一下" << '\n';
 77     }
 78 public:
 79     TiaoDaXia() {};
 80     TiaoDaXia(DaXia *dx)
 81         :ZhuangShiDaXia(dx)
 82     {}
 83     virtual ~TiaoDaXia()
 84     {}
 85     virtual void GongJi()
 86     {
 87         Tiao();
 88         daxia->GongJi();   //具體的攻擊功能交由所聚合的類完成
 89     }
 90 
 91     //YinShen功能徹底採用基類版本,即daxia->YinShen(),仍是交由所聚合的類完成
 92 };
 93 
 94 class ShuoDaXia :public ZhuangShiDaXia   //具備說裝飾的大俠
 95 {
 96 protected:
 97     virtual void Shuo()   //實現說裝飾
 98     {
 99         cout << "你來找我呀?!" << '\n';
100     }
101 public:
102     ShuoDaXia() {};
103     ShuoDaXia(DaXia *dx)
104         :ZhuangShiDaXia(dx)
105     {}
106     virtual ~ShuoDaXia()
107     {}
108     virtual void YinShen()
109     {
110         Shuo();
111         daxia->YinShen();   //具體的隱身功能交由所聚合的類完成
112     }
113 
114     //GongJi功能徹底採用基類版本,即daxia->GongJi(),仍是交由所聚合的類完成
115 };
116 
117 int main()
118 {
119     ZhangFengXia *zfx = new ZhangFengXia();   //張鳳霞
120     ShuoDaXia *sdx = new ShuoDaXia(zfx);   //將張鳳霞「嵌入」到具備說裝飾的大俠實例中,構造出具備說裝飾的張鳳霞
121     TiaoDaXia *tdx = new TiaoDaXia(sdx);//將具備說裝飾的張鳳霞「嵌入」到具備跳裝飾的大俠實例中,使得最終的大俠具備跳裝飾和說裝飾
122     tdx->GongJi();//攻擊
123     tdx->YinShen();//隱身
124     delete tdx;
125     tdx = NULL;
126     sdx = NULL;
127     zfx = NULL;
128 
129     return 0;
130 }

趙四(洋洋得意地):來,走一波!(趙四運行了程序)

趙四(自豪地):看到了吧,咱們構造出的大俠具備了跳裝飾和說裝飾。

劉能(懵懂地):仍是不太懂……

趙四(故做不耐煩):好吧,我就再啓發你一下。(又拿出一張紙)

趙四:如上圖所示,ZhangFengXia實例嵌入到ShuoDaXia實例中(即ShuoDaXia的daxia成員指向ZhuangFengXia實例),ShuoDaXia實例嵌入到TiaoDaXia實例中。當執行tdx->GongJi()時,執行代碼

virtual void GongJi()  
{  
    Tiao();  
    daxia->GongJi();  //具體的攻擊功能交由所聚合的類完成   
}

 即先調用Tiao(),輸出「跳一下」,而後執行daxia指向的實例的GongJi()函數,而此時的daxia(即this->daxia)指向的是ShuoDaXia實例,而ShuoDaXia類中的GongJi()函數依然採用的是從它的基類ZhuangShiDaXia繼承過來的GongJi()函數,即依然執行daxia->GongJi(),而此時的daxia指向ZhangFengXia實例,因而調用ZhangFengXia類的GongJi()函數,輸出「來自張鳳霞的攻擊!」。

 tdx->YinShen()的執行相似。先執行TiaoDaXia類的YinShen()函數,而該函數徹底繼承自基類ZhuangShiDaXia,因而執行

virtual void YinShen()  
{  
    daxia->YinShen();  
}

而此時daxia指向ShuoDaXia實例,因而調用ShuoDaXia實例的YinShen()函數,即

virtual void YinShen()  
{  
    Shuo();  
    daxia->YinShen();   //具體的隱身功能交由所聚合的類完成  
}

因而先調用Shuo(),輸出「你來找我呀?!」,而後再調用daxia指向的實例的YinShen()函數,而此時的daxia指向ZhangFengXia實例,因而調用ZhangFengXia類的YinShen()函數,輸出「張鳳霞已隱身!」。

衆人(恍然大悟):哦,明白了!
小瀋陽(如有所思):也就是說,具體裝飾類只具體實現本身的裝飾部分,而其它的,都交由它所聚合的類完成。例如,對TiaoDaXia而言,它只實現具體的跳功能,並將其封裝進本身版本的GongJi()函數中,而具體的攻擊行爲和不禁它裝飾的隱身行爲,則通通採用它所聚合的類的版本。
宋小寶(搶着道):更爲關鍵的是,若是被聚合者也一樣含有daxia指針,便是ZhuangShiDaXia的子類的話,它還會聚合其它的類,直到被聚合者不含daxia指針,如SanXia類和ZhangFengXia類。對於趙四給出的Demo,能夠抽象出這樣的「類的嵌套模型」。(舉起手中的一張紙)

劉能:沒……沒錯!(指着趙四畫的UML類圖)經過繼承和聚合,DaXia類和ZhuangShiDaXia類構成一個環,因而就能夠實現類的「遞歸嵌套」了,而「遞歸出口」就是不含daxia指針的類。
小瀋陽:而這種「遞歸嵌套」的過程,就是功能組合的過程。分析趙四給出的Demo,不難看出,從代碼的層面來看,嵌套,或者說功能組合是由內而外的,而調用是由外而內的。若是調用到的方法是具體裝飾類本身的版本,則先執行相關的裝飾功能,而後將核心功能交給「下一層」,這樣「層層委託」,直到未經裝飾的類

劉能(激動地):我徹……完全懂了,我還能舉……舉一反反……三呢,看,若是想要一個在攻擊以前跳兩次的大俠,這……這麼寫就可……能夠了。(說着敲出以下代碼)

ZhangFengXia *zfx = new ZhangFengXia();   //張鳳霞  
TiaoDaXia *tdx1 = new TiaoDaXia(zfx);   //跳一下  
TiaoDaXia *tdx = new TiaoDaXia(tdx1);   //再跳一下  

趙四(摸着劉能的頭):孺子可教也!

劉能(扒拉開趙四的手):邊去!別摸我頭!

趙四(故做高深地):其實,還有更簡潔的寫法。(敲出以下代碼)

TiaoDaXia *tdx = new TiaoDaXia(new TiaoDaXia(new ZhangFengXia()));  
趙四:這麼寫更加簡潔,但不太好懂,因此一開始我是分開寫的。

小瀋陽(如有所思):你別說,趙四整的這個「裝飾模式」,還挺好的。咱們知道,當須要對一個類的功能進行擴展時,通常有聚合和繼承兩種方式,前者能夠減小新增長的類的數量,但可能會帶來與客戶端接口不兼容的問題,須要修改客戶端;後者雖然保持了接口的一致性,但在變化有多種組合時,子類數量激增。裝飾模式同時採用繼承和聚合,二者相輔相成、相生相剋,既保持了各自的優勢,又克服了對方的缺點,更重要的是,因爲同時使用了繼承和聚合,能夠動態地實現類的層次嵌套和功能的自由組合,很是的靈活

劉能:沒……沒錯!當咱們須要對核心功能進行裝飾,而這些裝飾又有不少變化和組合的時候,採用裝飾模式是極好的。並且,當增長新的變化時,徹底不用修改當前的代碼,只須要增長一個具體裝飾類就能夠。也就是說,咱們能夠很方便地、隨時隨地地擴展功能,簡直了,還有誰?very good!

宋小寶(不屑地撇嘴):得了,消停會兒吧,又不是你發明的!

趙四(故做深沉地):任何事物都有兩面性。拿裝飾模式來講,剛纔你們都說了它的優勢,卻忽略了它的不足。與單純的繼承相比,裝飾模式減小了項目中類的數量,但在具體應用時,卻增長了客戶端建立的對象的數量。假設咱們須要一個在攻擊前跳一下,在隱身前喊話的張鳳霞,若是採用繼承的方法,咱們會從ZhangFengXia類派生一個具備相應功能的NewZhangFengXia類,客戶端只須要建立一個NewZhangFengXia類的實例就能夠了;而若是採用裝飾模式,正如你們在我寫的Demo裏看到的,因爲咱們要組合、拼湊出相應的大俠,因此須要建立ZhangFengXia類的實例、ShuoDaXia類的實例、TiaoDaXia類的實例,共計3個實例。若是咱們的類很大很複雜,那麼,建立類的實例是比較耗時的,這樣會影響系統效率。另外,裝飾模式中對象之間是層層嵌套的,這就使得最終組合出來的對象比較複雜,一個調用會引發由外而內的一連串調用,一旦出現問題,咱們每每不知道問題具體出如今哪一個環節,只能逐層排查,顯然這是很麻煩的。再者,正如你們看到的,在裝飾模式下,客戶端構造大俠實例的代碼的可讀性比較差,對於一個不懂設計模式的人來講,是比較難看懂這些代碼的。因此,總的來講,在必定程度上,裝飾模式會下降項目的效率,增長項目的複雜度

小瀋陽:能夠呀,趙四,沒看出來啊!有兩把刷子!

(響起深沉傷感的音樂)

趙四(緩慢而深沉地):謝謝!(淚眼朦朧地眺望着遠方)記得那是2010年,家裏拿錢讓我上《非誠勿擾》。我剛一上場,24盞燈就呼啦全滅了。今後,我就開始了屢敗屢戰的相親征途。直到第999次相親失敗,我總算醒悟了,就我這副尊容,去相親,說白了就是浪費感情。萬念俱灰之下,我拿相親剩下的錢,買了一臺二手電腦,今後踏上了程序員這條不歸路。我不像大家,都是加里敦、地理賽這些名牌大學的高材生,我是半路出家。因而,我是時時受欺負、到處被鄙視,但是(使勁揮拳),我不甘心!雖然我尚未媳婦,但我也是個男人!我也有尊嚴!因此,我必定要用實力證實本身!因而,我想方設法、處心積慮、不擇手段地學習!終於,皇天不負苦心人,我作到了!

小瀋陽(揉着眼睛,嗚咽着說):太感人了!四兒呀,對不起,之前是大哥很差,之後,大哥不再欺負你了!

劉能(擤了把鼻涕):太……太勵志了!四兒呀,對……對不起,之前是二……二哥很差,之後,二哥不再鄙……鄙視你了!

(宋小寶悄悄向門口退去,卻不當心碰倒了垃圾桶,因而被小瀋陽和劉能發覺)

小瀋陽、劉能(追着跑在前面的宋小寶):宋小寶,你給我站住!

(完)

相關文章
相關標籤/搜索