第一場 難題未解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次相親失敗,我總算醒悟了,就我這副尊容,去相親,說白了就是浪費感情。萬念俱灰之下,我拿相親剩下的錢,買了一臺二手電腦,今後踏上了程序員這條不歸路。我不像大家,都是加里敦、地理賽這些名牌大學的高材生,我是半路出家。因而,我是時時受欺負、到處被鄙視,但是(使勁揮拳),我不甘心!雖然我尚未媳婦,但我也是個男人!我也有尊嚴!因此,我必定要用實力證實本身!因而,我想方設法、處心積慮、不擇手段地學習!終於,皇天不負苦心人,我作到了!
小瀋陽(揉着眼睛,嗚咽着說):太感人了!四兒呀,對不起,之前是大哥很差,之後,大哥不再欺負你了!
劉能(擤了把鼻涕):太……太勵志了!四兒呀,對……對不起,之前是二……二哥很差,之後,二哥不再鄙……鄙視你了!
(宋小寶悄悄向門口退去,卻不當心碰倒了垃圾桶,因而被小瀋陽和劉能發覺)
小瀋陽、劉能(追着跑在前面的宋小寶):宋小寶,你給我站住!
(完)