《Head First 設計模式》之裝飾者模式
前言:
時間過得挺快的,轉眼間就到了十月中旬了,再晃著晃著新的一年就要來臨。今天lz有幸來到了浙大圖書館來學習,想想自己已經畢業兩年了,今日再次踏進校園裏,心裏頗有一絲感悟,說不出來,只有把它放在心裏。lz前段時間看到一篇文章,覺得裏面有句話說的很好,想拿出來與大家分享。文章的標題是《在寫作中成長》,有句話是說,我們可以通過寫作來學習,學習方法是每個輸入都盡量有所輸出。 無論是讀書,看文章,參加技術講座,還是看電影,盡量或多或少的總結輸出出來,輸出形式要麽是一篇博客,要麽是一篇微博,要麽是一篇筆記。因為自己完全掌握的知識應該是自己能表達出來的知識。
今日分享:
老樣子,今天給大家帶來兩句話。
1.很喜歡這句話:你現在的氣質裏,藏著你走過的路,讀過的書和愛過的人。
2.一句英文,The only you in the world, ever if no one appreciate, should love and take care of yourself.世界上唯一的你,就算沒有人欣賞,也要好好愛自己。真正的美就是做自己,你不需要被大部分人認可,你只需要接受最真實的自我!
裝飾者模式
今天給大家帶來的是裝飾者模式。一說到復用我想大家腦子裏的第一反應應該就是繼承,通過對這個模式的學習,大家可以領悟到運行時期的擴展遠比編譯時期的繼承威力大。也即,我們可以采取對象組合的方式,做到在運行時裝飾類。貌似說的有點抽象哈,OK,下面繼續和大家探討裝飾者模式。
咖啡實例:
現給出一個具體的實例讓大家初步感受下裝飾者模式。某家咖啡連鎖店裏面有多種飲料供應,每種飲料也可以要求在其中加入各種調料,這家連鎖店會根據所加入的調料收取不同的費用。先畫出該案例的UML類圖:
UML:
下面給出具體代碼示例:
先給出被裝飾者角色的抽象類:
1 package xin.yangmj.decorate.decorated; 2 3 /** 4 * 這是飲料抽象類,角色相當於被裝飾者 5 * 6 * @author Eric Yang 7 * @create 2017-10-14 下午2:24 8 **/ 9 public abstractclass Beverage { 10 11 public String description = "Unknown Beverage"; 12 13 public String getDescription() { 14 return description; 15 } 16 17 public abstract double cost(); 18 }
再給出四個具體的飲料實現類:
1 package xin.yangmj.decorate.decorated.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 5 /** 6 * 一種具體的飲料 7 * 8 * @author Eric Yang 9 * @create 2017-10-14 下午2:30 10 **/ 11 public class DarkRoast extends Beverage { 12 13 public DarkRoast() { 14 description = "Dark Roast Coffee"; 15 } 16 17 @Override 18 public double cost() { 19 return 2.99; 20 } 21 }
1 package xin.yangmj.decorate.decorated.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 5 /** 6 * 一種具體的飲料 7 * 8 * @author Eric Yang 9 * @create 2017-10-14 下午2:30 10 **/ 11 public class Decaf extends Beverage { 12 13 public Decaf() { 14 description = "Decaf Coffee"; 15 } 16 17 @Override 18 public double cost() { 19 return 2.09; 20 } 21 }
1 package xin.yangmj.decorate.decorated.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 5 /** 6 * 這是濃縮咖啡,一種具體的飲料 7 * 8 * @author Eric Yang 9 * @create 2017-10-14 下午2:30 10 **/ 11 public class Espresso extends Beverage { 12 13 public Espresso() { 14 description = "Espresso Coffee"; 15 } 16 17 @Override 18 public double cost() { 19 return 1.99; 20 } 21 }
1 package xin.yangmj.decorate.decorated.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 5 /** 6 * 另一種飲料的實現 7 * 8 * @author Eric Yang 9 * @create 2017-10-14 下午2:34 10 **/ 11 public class HouseBlend extends Beverage { 12 13 public HouseBlend() { 14 description = "House Blend Coffee"; 15 } 16 17 @Override 18 public double cost() { 19 return 0.89; 20 } 21 }
再給出抽象調料裝飾者,為裝飾者角色:
1 package xin.yangmj.decorate.decorator; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 5 /** 6 * 這是調料抽象類,角色相當於裝飾者 7 * 8 * @author Eric Yang 9 * @create 2017-10-14 下午2:28 10 **/ 11 public abstract class CondimentDecorator extends Beverage { 12 13 public abstract String getDescription(); 14 }
再給出四個具體的調料裝飾者實現類:
1 package xin.yangmj.decorate.decorator.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 import xin.yangmj.decorate.decorator.CondimentDecorator; 5 6 /** 7 * 這是調料的一種實現 8 * 9 * @author Eric Yang 10 * @create 2017-10-14 下午2:54 11 **/ 12 public class Milk extends CondimentDecorator { 13 14 Beverage beverage; 15 16 public Milk(Beverage beverage) { 17 this.beverage = beverage; 18 } 19 20 /** 21 * 表明是該調料裝飾者飲料,這也解釋了 22 * 為什麽調料裝飾者要有個抽象的getDescription()方法 23 * 24 * @return 25 */ 26 @Override 27 public String getDescription() { 28 return beverage.getDescription() + ", Milk"; 29 } 30 31 /** 32 * 這是總價錢,包含包裝的飲料和此調料 33 * 34 * @return 35 */ 36 @Override 37 public double cost() { 38 return 0.30 + beverage.cost(); 39 } 40 }
1 package xin.yangmj.decorate.decorator.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 import xin.yangmj.decorate.decorator.CondimentDecorator; 5 6 /** 7 * 這是調料的一種實現 8 * 9 * @author Eric Yang 10 * @create 2017-10-14 下午2:35 11 **/ 12 public class Mocha extends CondimentDecorator { 13 14 Beverage beverage; 15 16 public Mocha(Beverage beverage) { 17 this.beverage = beverage; 18 } 19 20 /** 21 * 表明是該調料裝飾者飲料,這也解釋了 22 * 為什麽調料裝飾者要有個抽象的getDescription()方法 23 * 24 * @return 25 */ 26 @Override 27 public String getDescription() { 28 return beverage.getDescription() + ", Mocha"; 29 } 30 31 /** 32 * 這是總價錢,包含包裝的飲料和此調料 33 * 34 * @return 35 */ 36 @Override 37 public double cost() { 38 return 0.20 + beverage.cost(); 39 } 40 }
1 package xin.yangmj.decorate.decorator.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 import xin.yangmj.decorate.decorator.CondimentDecorator; 5 6 /** 7 * 這是調料的一種實現 8 * 9 * @author Eric Yang 10 * @create 2017-10-14 下午2:54 11 **/ 12 public class Soy extends CondimentDecorator { 13 14 Beverage beverage; 15 16 public Soy(Beverage beverage) { 17 this.beverage = beverage; 18 } 19 20 /** 21 * 表明是該調料裝飾者飲料,這也解釋了 22 * 為什麽調料裝飾者要有個抽象的getDescription()方法 23 * 24 * @return 25 */ 26 @Override 27 public String getDescription() { 28 return beverage.getDescription() + ", Soy"; 29 } 30 31 /** 32 * 這是總價錢,包含包裝的飲料和此調料 33 * 34 * @return 35 */ 36 @Override 37 public double cost() { 38 return 0.15 + beverage.cost(); 39 } 40 }
1 package xin.yangmj.decorate.decorator.impl; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 import xin.yangmj.decorate.decorator.CondimentDecorator; 5 6 /** 7 * 這是調料的一種實現 8 * 9 * @author Eric Yang 10 * @create 2017-10-14 下午2:54 11 **/ 12 public class Whip extends CondimentDecorator { 13 14 Beverage beverage; 15 16 public Whip(Beverage beverage) { 17 this.beverage = beverage; 18 } 19 20 /** 21 * 表明是該調料裝飾者飲料,這也解釋了 22 * 為什麽調料裝飾者要有個抽象的getDescription()方法 23 * 24 * @return 25 */ 26 @Override 27 public String getDescription() { 28 return beverage.getDescription() + ", Whip"; 29 } 30 31 /** 32 * 這是總價錢,包含包裝的飲料和此調料 33 * 34 * @return 35 */ 36 @Override 37 public double cost() { 38 return 0.18 + beverage.cost(); 39 } 40 }
最後給出客戶端測試代碼:
1 package xin.yangmj.decorate; 2 3 import xin.yangmj.decorate.decorated.Beverage; 4 import xin.yangmj.decorate.decorated.impl.DarkRoast; 5 import xin.yangmj.decorate.decorated.impl.Espresso; 6 import xin.yangmj.decorate.decorated.impl.HouseBlend; 7 import xin.yangmj.decorate.decorator.impl.Mocha; 8 import xin.yangmj.decorate.decorator.impl.Soy; 9 import xin.yangmj.decorate.decorator.impl.Whip; 10 11 /** 12 * 這是客戶端,用來測試供應咖啡 13 * 14 * @author Eric Yang 15 * @create 2017-10-14 下午2:50 16 **/ 17 public class StarbuzzCoffee { 18 public static void main(String[] args){ 19 Beverage beverage = new Espresso(); 20 // 沒有加任何調料 21 System.out.println(beverage.getDescription() + "--- $" + beverage.cost()); 22 23 // 用兩個Mocha裝飾它,然後再用Whip裝飾 24 Beverage beverage2 = new DarkRoast(); 25 beverage2 = new Mocha(beverage2); 26 beverage2 = new Mocha(beverage2); 27 beverage2 = new Whip(beverage2); 28 System.out.println(beverage2.getDescription() + "--- $" + beverage2.cost()); 29 30 // 分別用Soy, Mocha, Whip來裝飾 31 Beverage beverage3 = new HouseBlend(); 32 beverage3 = new Soy(beverage3); 33 beverage3 = new Mocha(beverage3); 34 beverage3 = new Whip(beverage3); 35 System.out.println(beverage3.getDescription() + "--- $" + beverage3.cost()); 36 } 37 }
運行結果如下:
認識裝飾者模式
通過上面的例子,我們可以以飲料為主體,然後在運行時以調料來“裝飾”(decorate)飲料。比如,顧客想要摩卡和奶泡深焙咖啡,那麽,要做的是:
1.拿一個深焙咖啡(DarkRoast)對象
2.以摩卡(Mocha)對象裝飾它
3.以奶泡(Whip)對象裝飾它
4調用cost()方法,並依賴委托(delegate)將調料的價錢加上去
定義裝飾者模式
官方定義:裝飾者模式動態地將責任附加到對象上,若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。具體UML類圖如下:
UML類圖
通過上面的定義和UML類圖,可以得出關於裝飾者模式的以下幾點結論:
1.裝飾者和被裝飾者對象有相同的超類型。
2.你可以用一個或多個裝飾者包裝一個對象。
3.既然裝飾者和被裝飾者對象有相同的超類型,所以可以在任何需要原始對象(被包裝的)的場合,都可以用裝飾過的對象來替代它。
4.裝飾者可以在所委托被裝飾者的行為之前/或之後,加上自己的行為,以達到特定的目的。
5.對象可以在任何時候被裝飾,所以可以在運行時動態地,不限量地用你喜歡的裝飾者來裝飾對象。
滿足的設計原則
開放-關閉原則:類應該對擴展開發,對修改關閉。可以理解為,允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行為。這樣子的設計具有彈性可以應對改變,可以接受新的功能來應對改變的需求。具體在本例中,我們可以任意增加飲料和調料的種類,方便此咖啡店對它們的產品進行擴展,並且無需對現有的代碼進行改變。只要新增加的種類滿足現有的規範即可,也即:實現裝飾者和被裝飾者抽象類即可!
答惑:在此特別和大家說明一點,裝飾者模式最重要的一點是:裝飾者需要和被裝飾者(被包裝的組件)有相同的“接口”,因為,裝飾者必須能取代被裝飾者。也即:這兩者必須是一樣的類型,具有共同的超類。在這裏,我們利用繼承達到“類型匹配”,而不是利用繼承獲得“行為”。行為來自裝飾者和基礎組件,或與其他裝飾者之間的組合關系。
真實世界的裝飾者:Java I/O
此處有待後續補充...
《Head First 設計模式》之裝飾者模式