本文由@呆代待殆原創,轉載請注明出處。
此設計模式遵循的設計原則之一:類應該支持擴展,而拒絕修改(Open-Closed Principle)
裝飾者模式簡述
裝飾者模式通過組合的方式擴展對象的特性,這種方式允許我們在任何時候對對象的功能進行擴展甚至是運行時擴展,而若我們用繼承來完成對類的擴展則只能在編譯階段實現,所以在某些時候裝飾者模式比繼承(inheritance)要更加靈活。
裝飾者模式具有的一些特征
1,裝飾者(decorator)和被裝飾(擴展)的對象有着相同的超類(supertype)。
2,我們可以用多個裝飾者去裝飾一個對象。
3,我們可以用裝飾過的對象替換代碼中的原對象,而不會出問題(因為他們有相同的超類)。
4,裝飾者可以在委托(delegate,即調用被裝飾的類的成員完成一些工作)被裝飾者的行為完成之前或之后加上他自己的行為。
5,一個對象能在任何時候被裝飾,甚至是運行時。
裝飾者模式的基本結構
我們來看一張《Head first 設計模式》里的圖
(圖中的英文為書中對這個結構的解釋與說明,下面的解釋並不是對圖中英文的直接翻譯,而是博主自己的稍稍結合書中其他內容的總結)
Component:一般是一個抽象類(也有可能不是),是一組有着某種用途類的基類,包含着這些類最基本的特性。
ConcreteComponent:繼承自Component,一般是一個有實際用途的類,這個類就是我們以后要裝飾的對象。
Decorator:繼承自Component,裝飾者需要共同實現的接口(也可以是抽象類),用來保證裝飾者和被裝飾者有共同的超類,並保證每一個裝飾者都有一些必須具有的性質,如每一個裝飾者都有一個實例變量(instance variable)用來保存某個Component類型的類的引用。
ConcreteDecorator:繼承自Decorator,用來裝飾Component類型的類(不能裝飾抽象類),為其添加新的特性,可以在委托被裝飾者的行為完成之前或之后的任意時候。
一個簡單的實例
我們用《Head First 設計模式》里舉的星巴克訂單的例子來說明。
星巴克提供不同種類的咖啡和咖啡的調料,星巴克需要一些類來描述他們並且能計算出任意一種咖啡和任意幾種調料搭配在一起的價格,如果我們用繼承為每一種搭配寫一個類的話,就會變成下面這個樣子。
要在工作中用這種東西,還不如讓我們一起狗帶_(:зゝ∠)_。。。。。
當然我們也可把調料都寫在作為超類Beverage里,但是這樣的話會造成數據的大量冗余,這是一個解決辦法,但是還不夠好。
如果套用上面所介紹的裝飾者模式的結構就是下面這個樣子
Beverage(飲料類):相當與Component
HouseBlend、DarkRoast...(混合咖啡類、礁炒咖啡類...):相當於ConcreteComponent
CondimentDecorator(調料裝飾者類):相當於Decorator
Milk、Mocha...(牛奶類、摩卡類...):相當於ConcreteDecorator
裝飾者模式的特點,一個ConcreteComponent可以被任意個ConcreteDecorator裝飾。
結合實例就來解釋,一種 咖啡 可以和任意種 調料 搭配。
這樣我們來應對各種點單的搭配的時候只需要在一種咖啡(ConcreteComponent)加上各種(ConcreteDecorator)就可以完成了,是不是覺得特別方便。
java實現-裝飾者模式-星巴克
1 public abstract class Beverage { 2 protected String description=""; 3 public String getDescription(){ 4 return description; 5 } 6 public abstract double cost(); 7 }
1 public abstract class CondimentDecorator extends Beverage { 2 public abstract String getDescription(); 3 4 }
1 public class HouseBlend extends Beverage { 2 public HouseBlend(){ 3 description="House Blend coffee"; 4 } 5 @Override 6 public double cost() { 7 return 4.9; 8 } 9 }
1 public class Milk extends CondimentDecorator { 2 protected Beverage beverage; 3 public Milk(Beverage beverage){ 4 this.beverage=beverage; 5 } 6 @Override 7 public String getDescription() { 8 return beverage.getDescription()+",with milk"; 9 } 10 @Override 11 public double cost() { 12 return 2.3+beverage.cost(); 13 } 14 }
1 public class Mocha extends CondimentDecorator { 2 protected Beverage beverage; 3 public Mocha(Beverage beverage){ 4 this.beverage=beverage; 5 } 6 @Override 7 public String getDescription() { 8 return beverage.getDescription()+",with Mocha"; 9 } 10 @Override 11 public double cost() { 12 return 1.2+beverage.cost(); 13 } 14 }
1 public class Starbuzz { 2 public static void main(String[] args) { 3 Beverage beverage=new Mocha(new Milk(new Mocha(new HouseBlend()))); 4 //如上就是一杯HouseBlend配上兩份Mocha和一份Milk,博主沒去星巴克喝過咖啡,這樣配可以么= = 5 System.out.println(beverage.getDescription()+":"+beverage.cost()); 6 } 7 8 }
輸出如下
House Blend coffee,with Mocha,with milk,with Mocha:9.6
繼承與裝飾者模式
ps:裝飾者模式中用到了繼承,但是這里裝飾者模式用繼承是為了保證裝飾者和被裝飾者有共同的超類,而不是為了給被裝飾的類擴展新的特性,而裝飾者模式中新的特性是通過類與類的組合(has-a的關系)而得到的,所以把裝飾者模式和繼承看做同一類設計思想是不恰當的。
不適合使用裝飾者模式的情況
當你的有些設計是針對某些具體的Component類型即ConcreteComponent時,裝飾者模式是不適用的,因為被裝飾過的類是通過組合來實現特性的擴展,所以裝飾過的類並不具有某個特定的ConcreteComponent類型,而只具有其本身的類型和它繼承的Decorator以及Component的類型,所以只有當你的操作都是針對Component類型的時候,裝飾者模式才是合適的,否則你就要重新考慮你的設計。
裝飾者設計模式的缺點
會引入大量的類,常常讓人覺得不知所措,特別是對於初學者來說,最好的例子就是java的I/O類,如果你翻一翻API你就會看到多的想讓你摔電腦的類名,但是,博主覺得即使存在這個缺點,但裝飾者模式仍然是一個值得我們學習和應用的好設計模式。
參考資料:《Head First 設計模式》