前言
在食品工業中的裝飾圖案具有比較廣泛的應用,大多數的兩個圖案和在網上的例子飲食相關的,一旦被稱為電影的手錶,點咖啡要加糖要加奶昔要加這加那的時候。感覺好有派~好高大上啊~。為啥我在小賣部都是“來瓶汽水”就沒話說了呢~,難道是我不會“裝”?
官方定義
動態的給一個物件加入一些職責,就新增功能來說。該模式比生成子類更為靈活——GOF
Decorator模式是一種相對簡單的物件結構性模式,動態和物件是個相應的關係。正如靜態和類這種相應關係,編譯時可以決定的特質是靜態特質,動態則表示在執行時進行操作,傳統情況下使用的繼承是靜態的給類加入職責。動態的給物件加入職責。則是裝飾者模式所要完畢的事。
給類和給物件加入職責有什麼不同呢,前者的靈活性要差。假設須要加入的職責非常多,前者須要為每種情況都定義一個固定類,這裡的每種情況指的是職責的排列組合,假如我要為一個原始類加入5個職責。則會出現5的階乘種情況,不不過類爆炸帶來的繁瑣,執行時對職責的改動是隨意的,這就使得編譯時確定的類又要在執行時頻繁改變,而直接往物件中加入職責則使的結構能夠靈活多變,相同的情況,我只須要額外新增5個修飾類就能夠完美解決類爆炸及執行時改變的情況,這就是上述裝飾者模式定義所要表達的詳細含義。
場景
產生類爆炸的原因非常多,並非每種情況都能夠用裝飾者模式來解決,該模式應用的典型場景需滿足下面三點
1 原始元件(被裝飾者)和裝飾者符合邏輯上的修飾關係時
這個比較好理解,HeadFirst設計模式舉了個為咖啡加入不同口味的配料,不同口味的配料相應一個裝飾者,通常來講。程式碼結構要反映出邏輯關係。不管是哪種口味的配料,他對咖啡物件確實產生了修飾關係,網上關於裝飾者模式的舉例大部分是關於飲食類,這是有道理的,不同口味或者配料相應不同的裝飾者。這樣是符合現實世界的邏輯關係的。
2 裝飾者之間是能夠獨立存在的
還是繼續咖啡的樣例,每種口味之間都是獨立關係,互相之間沒有依賴關係。這個依據客戶的需求。有人喜歡摩卡口味。有人喜歡奶油口味,這兩種修飾者能夠獨立修飾咖啡的。當然有人喜歡摩卡奶油咖啡,這個不過修飾順序的問題,不影響兩種口味的獨立性。假設修飾者之間不滿足這樣的獨立性。使用裝飾者模式是不合理得。
3 當無法確定原始元件被裝飾的方式和時機時
一杯咖啡,依據客戶需求的不同,加入不同口味的配料。客戶會喜歡那種口味或者哪幾種,事先是不確定的,咖啡店也不須要事先確定。他們會把不同口味的配料放在不同的機器中,隨要隨取。靈活應對需求,假設須要新口味。加入一個新機器就能夠,擴充套件靈活,至少這些咖啡店的老闆。都是懂裝飾者模式的。
舉例
裝飾者模式的經典樣例非常多,沒有必要反覆造輪子,就接著咖啡這個樣例來說吧。
裝飾者模式最核心的部分,就是從下圖開始
一杯咖啡代表被裝飾的元件component,咖啡與被輔料之間有兩種關係,組合與繼承。IS-A和Has-A的關係,HAS-A比較好理解,通過組合一個被裝飾物件。能夠使用被裝飾物件的操作並進行裝飾,IS-A則和通常情況有差別。輔料(Decorator)本質上並非咖啡。所以這裡的繼承關係並非類繼承。而是介面(協議)繼承。其含義就是經過輔料修飾後的咖啡仍然具有咖啡本身所含有的介面特性,也就是描寫敘述和價格,不能把這個繼承關係理解成調料也是一種咖啡,之所以使用繼承的原因是能夠方便靈活的在執行時動態加入職責,後面的測試例結果能夠看出這一點。
和上述UML圖對於的原始碼例如以下
Cafe.java
package com.klpchan.example.cafe; public abstract class Cafe { public String getDescription() {
return description;
} public abstract float getPrice(); String description = "This is Cafe";
}
Decorator.java
package com.klpchan.example.cafe; public abstract class Decorator extends Cafe{ public Decorator(Cafe _cafe) {
// TODO Auto-generated constructor stub
this.cafe = _cafe;
} @Override
public String getDescription() {
// TODO Auto-generated method stub
return cafe.getDescription();
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return cafe.getPrice();
} Cafe cafe;
}
咖啡作為被修飾者。含有非常多詳細的子類,顧客選擇時首先要選擇詳細的某種咖啡(詳細component)。然後在為其選擇口味(詳細Decorator),本例選擇脫脂(DECAF)和意式(Espresso)兩種咖啡,第二圖UML圖誕生了
兩種詳細的咖啡是被修飾的詳細類,各自己定義了咖啡的價格和說明,原始碼例如以下
DeCaf.java
package com.klpchan.example.cafe; public class DeCaf extends Cafe{ public DeCaf() {
// TODO Auto-generated constructor stub
description = "This is DECAF cofe";
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return Constants.CAFE_DECAF_PRICE;
}
}
Espresso.java
package com.klpchan.example.cafe; public class Espresso extends Cafe{ public Espresso() {
// TODO Auto-generated constructor stub
description = "This is Espresso " ;
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return Constants.CAFE_ESPRESSO_PRICE;
}
}
選擇好咖啡後,使用者開始選擇口味配料(Decorator),這就是裝飾模式的核心。本比如果有三種配料
摩卡(Mocha)、奶油(Milk)和巧克力(Chocolate),每種配料都有各自的價格,價格表詳細例如以下
package com.klpchan.example.cafe; public class Constants {
//脫脂和意式兩種咖啡的基本價格
public static final float CAFE_DECAF_PRICE = 8;
public static final float CAFE_ESPRESSO_PRICE = 9; //摩卡、牛奶、巧克力三種口味調料的價格
public static final float DECORATOR_MOCHA_PRICE = 0.5f;
public static final float DECORATOR_MILK_PRICE = 0.4f;
public static final float DECORATOR_CHOCOLATE_PRICE = 0.8f;
}
通過加入詳細的配料類來修飾咖啡,UML圖例如以下
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQva2xwY2hhbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center">
加入了三個詳細的配料類摩卡、奶油和巧克力,原始碼例如以下
MochaDecorator.java
package com.klpchan.example.cafe; public class MochaDecorator extends Decorator{ public MochaDecorator(Cafe _cafe) {
super(_cafe);
// TODO Auto-generated constructor stub
} @Override
public String getDescription() {
// TODO Auto-generated method stub
return super.getDescription() + " add mocha ";
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return super.getPrice() + Constants.DECORATOR_MOCHA_PRICE;
}
}
MilkDecorator.java
package com.klpchan.example.cafe; public class MilkDecorator extends Decorator{ public MilkDecorator(Cafe _cafe) {
super(_cafe);
// TODO Auto-generated constructor stub
} @Override
public String getDescription() {
// TODO Auto-generated method stub
return super.getDescription() + " add milk ";
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return super.getPrice() + Constants.DECORATOR_MILK_PRICE;
}
}
ChocolateDecorator.java
package com.klpchan.example.cafe; public class ChocolateDecorator extends Decorator{ public ChocolateDecorator(Cafe _cafe) {
super(_cafe);
// TODO Auto-generated constructor stub
} @Override
public String getDescription() {
// TODO Auto-generated method stub
return super.getDescription() + " add chocolate ";
} @Override
public float getPrice() {
// TODO Auto-generated method stub
return super.getPrice() + Constants.DECORATOR_CHOCOLATE_PRICE;
}
}
在每個詳細的修飾類中,新方法使用了被修飾物件本身的方法並加上了新的特性,本例中是加上了說明和配料價格,新方法能夠用在被修飾物件方法的前或者後,能夠動態改變被修飾物件的狀態和操作。被修飾物件(咖啡)並不須要知道修飾者(調料)究竟做了那些操作,這些都是透明的。
在客戶檔案寫了個測試比例如以下
Cafe cafe = new DeCaf();
Cafe mochaChocCafe = new MochaDecorator(new ChocolateDecorator(cafe));
System.out.println(mochaChocCafe.getDescription() + " Price is " + mochaChocCafe.getPrice()); Cafe cafe2 = new Espresso();
Cafe milkChocMochaCafe = new MochaDecorator(new ChocolateDecorator(new MilkDecorator(cafe2)));
System.out.println(milkChocMochaCafe.getDescription() + " Price is " + milkChocMochaCafe.getPrice());
能夠看出對於一個選定的咖啡類,使用者能夠自由組合其裝飾者,這就是較繼承靈活的體現,執行結果例如以下
This is DECAF cofe add chocolate add mocha Price is 9.3
This is Espresso add milk add chocolate add mocha Price is 10.7
對於上述原始碼中的價格表及測試原始碼,能夠看出兩個訂單的說明和價格均被正確改動完畢。
適用性
1 以動態透明的方式加入職責,動態前文已提及,透明是指被修飾者不依賴與修飾者,詳細咖啡不依賴與調料,以脫脂咖啡為例,該類不須要關係是摩卡還是巧克力來修飾自己。更不關心這些修飾類怎樣來修飾自己。程式碼結構中能夠表現為咖啡類並不存在修飾類的引用。
2 處理能夠撤銷的職責。相當於上述過程的逆向project。將職責模組化,能夠在執行時動態新增刪除。
3 當無法使用繼承來加入職責時。除了前文所述的因為職責過多引起的類爆炸。也可能是因為類定義被隱藏使得無法生成子類。
結構

這個和上述UML圖的結構基本吻合。
Component 被修飾的元件抽象。本例中表示咖啡類,正如前文所述,裝飾者和被裝飾者須要同一時候繼承這個抽象類,這樣能夠動態的為物件加入職責。
ConcreteComponent 詳細的被修飾物件。本例中有脫脂咖啡和意式咖啡,詳細元件不關心裝飾類怎樣裝飾他們。
Decorator 裝飾者抽象,本例中的調料基類,和元件是繼承與組合的關係。裝飾基類原封不動的呼叫被裝飾者的操作。供詳細修飾者重寫。
ConcreteDecoratorA/B 詳細裝飾者。本例中的摩卡、牛奶、巧克力裝飾類。能夠改動被修飾者狀態和行為。
效果
1 比靜態繼承更加靈活,前文已述。
2 避免在較高層次有較多特徵,檢視UML結構,元件和裝飾者都有著較為簡單的特徵和操作。在使用該模式時。GOF建議為簡單的類逐步加入功能而不是直接修飾一個複雜的類。由於逐步加入的功能能夠組合出新的複雜功能。而直接擴充套件複雜的類easy暴露非常多與職責無關的細節,應該儘量保持元件的簡潔性,元件應該做的是定義介面一類的工作,本例中的元件Cafe,只定義了主要的說明操作,由於裝飾者也須要繼承元件,在元件中定義過多且與職責沒用的功能,會新增裝飾者類的複雜程度,這段內容我理解的也不透徹,歡迎分享討論~
3 Decorator與Component不一樣。前文已述。被裝飾的元件與裝飾者的繼承關係為介面繼承,並不是類繼承,這樣做的目的是有利於多次修飾。
我們能夠使用一個或多個裝飾者來裝飾一個物件,裝飾後的物件仍然是component物件。
4 有很多小物件,這個能夠參閱本文上述的測試程式,不同的裝飾組合會產生的不同的物件,這個是裝飾者模式的缺點。
5 為了解決擴充套件職責所帶來的類爆炸和靈活性問題,該模式使用組合而非繼承的格式,繼承所產生的大量子類難以維護。更無法應對職責改變所引起的擴充套件性問題。
和其他模式的關係
介面卡模式是改變一個物件的介面型別,使他成為可以滿足與其他介面相匹配的要求。而裝飾者模式是為物件動態透明的加入職責。
裝飾者模式改變外殼,策略模式改變核心。事實上物件的外殼和核心是個相對的觀點,類似於本例中的咖啡,咖啡的外殼改變就是前文所說口味的變化,口味變化並不影響咖啡的本質,意式咖啡怎樣加糖也成為不了脫脂咖啡。而假設把意式咖啡在製作過程中的一些工藝替換了,則改變了核心,製作工藝的不同是意式咖啡和其他型別咖啡本質的差別,此時使用策略模式比較合理。這就是兩者的差別。
應用場景
.NET框架中的裝飾者模式應用,結構清晰不贅述了。
收尾
裝飾者模式是較易理解的經常使用結構性模式,通過組合而非繼承來解決原始類動態加入職責所引起的問題,本文偷個懶,直接改寫了HEAD First設計模式的樣例,此類樣例解釋的比較形象。對於一些較為晦澀的理論給出了自己的理解。模式這樣的東西每一個人都會有不同的理解,細節上會有差異,歡迎分享交流,共同進步~
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
注:歡迎分享。轉載請宣告~~
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
版權宣告:本文博主原創文章,部落格,未經同意不得轉載。