第17章 裝飾模式
17.1 罪惡的成績單
“中庸”是中國儒教文化的集中體現,說話或做事情都不能太直接,需要有技巧。比如談話,如果你要批評某個人,你不能一上來就說他這做得不對,那也做得不對,你要先肯定他的成績,表揚一下優點;然后再指出不足,指出錯誤的地方,最后再來點激勵,你修改了這些缺點后有哪些好處,比如你能帶更多的小兵、升職等。如果你一上來就是一頓批評,你瞅瞅看,對方肯定是不服氣,甚至是頂撞你說:“此處不養爺,自有養爺處”,于是甩門而去。
這是說話,做事情也是一樣。在山寨產品流行之前,假貨也是比較“盛行”的。本人2002年買了一部手機,當時老板吹得天花亂墜,承諾這部手機是最新的,我看著也像,殼子是嶄新的,包裝是嶄新的,沒有任何瑕疵,就是比正品便宜了很多,于是我買了,因為缺錢啊!用了3個月,壞了,送修檢查,結果診斷出這是新殼裝舊機,我暈!拿一部舊手機的線路板,找個新的外殼、屏幕、包裝就成了新手機,害人不淺啊!
我們不說不開心的事情,今天以什么例子為開場白呢?就說說我上小學的糗事吧。我上小學的時候學習成績非常差,班級上有40多個同學,我基本上都是排在45名以后,按照老師給我的評價就是:“不是讀書的料”。但是我父親管得很嚴格,明知道我不是這塊料,還是“趕鴨子上架”,每次考完試我都戰戰兢兢,“竹筍炒肉”是肯定少不了的,但是能少點就少點吧,因為肉可是自己的。四年級期末考試考完,學校出來個很損的招兒(這招兒現在很流行的),打印出成績單,要家長簽字,然后才能上五年級,我那個恐懼呀,不過也就是幾秒鐘的時間,玩起來什么都忘記了。我們做架構,做設計,任何值得我們回憶的事件都可以通過設計記錄下來。當然了,這份成績單的事情也是可以通過類圖表示的,如圖17-1所示。

圖17-1 成績單類圖
成績單的抽象類,然后有一個四年級的成績單實現類,So Easy,我們先來看抽象類,如代碼清單17-1所示。
代碼清單17-1 抽象成績單
public?abstract?class?SchoolReport?{
?????//成績單主要展示的就是你的成績情況
?????public?abstract?void?report();?????
?????//成績單要家長簽字,這個是最要命的
?????public?abstract?void?sign();
}
有抽象類了,我們再來看看具體的四年級成績單FouthGradeSchoolReport,如代碼清單17-2所示。
代碼清單17-2 四年級成績單
public?class?FouthGradeSchoolReport?extends?SchoolReport?{
?????//我的成績單
?????public?void?report()?{
?????????????//成績單的格式是這個樣子的
?????????????System.out.println("尊敬的XXX家長:");??
?????????????System.out.println("??......");
?????????????System.out.println("??語文?62??數學65?體育?98??自然??63");
?????????????System.out.println("??.......");
?????????????System.out.println("???????????????家長簽名:???????");
?????}
?????//家長簽名
?????public?void?sign(String?name)?{
?????????????System.out.println("家長簽名為:"+name);
?????}
}
成績單出來,你別看什么62、65之類的成績,你要知道,在小學低于90分基本上就是中下等了,悲哀呀,愛學習的人咋就那么多!怎么著,那我把這個成績單給老爸看看?好,我們修改一下類圖,成績單給老爸看,如圖17-2所示。

圖17-2 老爸查看成績單類圖
老爸開始看成績單,這個成績單可是最真實的,啥都沒有動過,原裝,Father類如代碼清單17-3所示。
代碼清單17-3 老爸查看成績單
public?class?Father?{
?????public?static?void?main(String[]?args)?{
?????????????//把成績單拿過來
?????????????SchoolReport?sr?=?new?FouthGradeSchoolReport();
?????????????//看成績單
?????????????sr.report();
?????????????//簽名?休想!
?????}
}
運行結果如下:
尊敬的XXX家長:
......
語文 62 數學65 體育 98 自然 63
.......
家長簽名:
就這成績還要我簽字?!老爸就開始找掃帚,我開始做準備:深呼吸,繃緊肌肉,提臀收腹。哈哈,幸運的是,這個不是當時的真實情況,我沒有直接把成績單交給老爸,而是在交給他之前做了點技術工作,我要把成績單封裝一下,封裝分類兩步來實現,如下所示。
● 匯報最高成績
跟老爸說各個科目的最高分,語文最高是75,數學是78,自然是80,然后老爸覺得我的成績與最高分數相差不多,考的還是不錯的嘛!這個是實情,但是不知道是什么原因,反正期末考試都考得不怎么樣,但是基本上都集中在70分以上,我這60多分基本上還是墊底的角色。
● 匯報排名情況
在老爸看完成績單后,告訴他我在全班排第38名,這個也是實情,為啥呢?有將近十個同學退學了!這個情況我是不會說的。不知道是不是當時第一次發成績單時學校沒有考慮清楚,沒有寫上總共有多少同學,排第幾名,反正是被我鉆了個空子。
那修飾是說完了,我們看看類圖如何修改,如圖17-3所示。

圖17-3 修飾成績單
我想這是大家最容易想到的類圖,通過直接增加了一個子類,重寫report方法,很容易地解決了這個問題,是不是這樣?是的,這確實是一個比較好的辦法,我們來看具體的實現,如代碼清單17-4所示。
代碼清單17-4 修飾成績單
public?class?SugarFouthGradeSchoolReport?extends?FouthGradeSchoolReport?{
?????//首先要定義你要美化的方法,先給老爸說學校最高成績
?????private?void?reportHighScore(){
?????????????System.out.println("這次考試語文最高是75,數學是78,自然是80");
?????}
?????//在老爸看完畢成績單后,我再匯報學校的排名情況
?????private?void?reportSort(){
?????????????System.out.println("我是排名第38名...");
?????}
?????//由于匯報的內容已經發生變更,那所以要重寫父類
?????@Override
?????public?void?report(){
?????????????this.reportHighScore();??//先說最高成績
?????????????super.report();??//然后老爸看成績單
?????????????this.reportSort();?//然后告訴老爸學習學校排名
?????}
}
然后對Father類稍做修改就可以看到美化后的成績單,如代碼清單17-5所示。
代碼清單17-5 老爸查看修飾后的成績單
public?class?Father?{
?????public?static?void?main(String[]?args)?{
?????????????//把美化過的成績單拿過來
?????????????SchoolReport?sr=?new?SugarFouthGradeSchoolReport();
?????????????//看成績單
?????????????sr.report();
?????????????//然后老爸,一看,很開心,就簽名了
?????????????sr.sign("老三");??//我叫小三,老爸當然叫老三
?????}
}
運行結果如下所示:
這次考試語文最高是75,數學是78,自然是80
尊敬的XXX家長:
......
語文 62 數學65 體育 98 自然 63
.......
家長簽名:
我是排名第38名...
家長簽名為:老三
通過繼承確實能夠解決這個問題,老爸看成績單很開心,然后就給簽字了,但現實的情況是很復雜的,可能老爸聽我匯報最高成績后,就直接樂開花了,直接簽名了,后面的排名就沒必要看了,或者老爸要先看排名情況,那怎么辦?繼續擴展?你能擴展多少個類?這還是一個比較簡單的場景,一旦需要裝飾的條件非常多,比如20個,你還通過繼承來解決,你想象的子類有多少個?你是不是馬上就要崩潰了!
好,你也看到通過繼承情況確實出現了問題,類爆炸,類的數量激增,光寫這些類不累死你才怪,而且還要想想以后維護怎么辦,誰愿意接收這么一大攤本質相似的代碼維護工作?并且在面向對象的設計中,如果超過兩層繼承,你就應該想想是不是出設計問題了,是不是應該重新找一條康莊大道了,這是經驗值,不是什么絕對的,繼承層次越多以后的維護成本越多,問題這么多,那怎么辦?好辦,我們定義一批專門負責裝飾的類,然后根據實際情況來決定是否需要進行裝飾,類圖稍做修正,如圖17-4所示。

圖17-4 增加專門的裝飾類圖
增加一個抽象類和兩個實現類,其中Decorator的作用是封裝SchoolReport類,如果大家還記得代理模式,那么很容易看懂這個類圖,裝飾類的作用也就是一個特殊的代理類,真實的執行者還是被代理的角色FouthGradeSchoolReport,如代碼清單17-6所示。
代碼清單17-6 修飾的抽象類
public?abstract?class?Decorator?extends?SchoolReport{
?????//首先我要知道是哪個成績單
?????private?SchoolReport?sr;
?????//構造函數,傳遞成績單過來
?????public?Decorator(SchoolReport?sr){
?????????????this.sr?=?sr;
?????}
?????//成績單還是要被看到的
?????public?void?report(){
?????????????this.sr.report();
?????}
?????//看完還是要簽名的
?????public?void?sign(String?name){
?????????????this.sr.sign(name);
?????}
}
看到沒,裝飾類還是把動作的執行委托給需要裝飾的對象,Decorator抽象類的目的很簡單,就是要讓子類來封裝SchoolReport的子類,怎么封裝?重寫report方法!先看HighScoreDecorator實現類,如代碼清單17-7所示。
代碼清單17-7 最高成績修飾
public?class?HighScoreDecorator?extends?Decorator?{
?????//構造函數
?????public?HighScoreDecorator(SchoolReport?sr){
?????????????super(sr);
?????}
?????//我要匯報最高成績
?????private?void?reportHighScore(){
?????????????System.out.println("這次考試語文最高是75,數學是78,自然是80");
?????}
?????//我要在老爸看成績單前告訴他最高成績,否則等他一看,就掄起掃帚揍我,我哪里還有機會說啊
?????@Override
?????public?void?report(){
?????????????this.reportHighScore();
?????????????super.report();
?????}
}
重寫了report方法,先調用具體裝飾類的裝飾方法reportHighScore,然后再調用具體構件的方法,我們再來看怎么匯報學校排序情況SortDecorator代碼,如代碼清單17-8所示。
代碼清單17-8 排名情況修飾
public?class?SortDecorator?extends?Decorator?{
?????//構造函數
?????public?SortDecorator(SchoolReport?sr){
?????????????super(sr);
?????}
?????//告訴老爸學校的排名情況
?????private?void?reportSort(){
?????????????System.out.println("我是排名第38名...");
?????}??
?????//老爸看完成績單后再告訴他,加強作用
?????@Override
?????public?void?report(){
?????????????super.report();
?????????????this.reportSort();
?????}
}
我準備好了這兩個強力的修飾工具,然后就“毫不畏懼”地把成績單交給老爸,看看老爸怎么看成績單的,如代碼清單17-9所示。
代碼清單17-9 老爸查看修飾后的成績單
public?class?Father?{
?????public?static?void?main(String[]?args)?{
?????????????//把成績單拿過來
?????????????SchoolReport?sr;
?????????????//原裝的成績單
?????????????sr?=?new?FouthGradeSchoolReport();
?????????????//加了最高分說明的成績單
?????????????sr?=?new?HighScoreDecorator(sr);
?????????????//又加了成績排名的說明
?????????????sr?=?new?SortDecorator(sr);
?????????????//看成績單
?????????????sr.report();
?????????????//然后老爸一看,很開心,就簽名了
?????????????sr.sign("老三");??//我叫小三,老爸當然叫老三
?????}
}
老爸一看成績單,聽我這么一說,非常開心,兒子有進步呀,從40多名進步到30多名,進步很大,躲過了一頓海扁。想想看,如果我還要增加其他的修飾條件,是不是就非常容易了,只要實現Decorator類就可以了!這就是裝飾模式。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖