用來解決上述問題的一個合理的解決方案,就是使用裝飾模式。那麼什麼是裝飾模式呢?html
(1)裝飾模式定義測試
(2)應用裝飾模式來解決的思路this
雖然通過簡化,業務簡單了不少,可是須要解決的問題不會少,仍是要解決:要透明的給一個對象增長功能,並實現功能的動態組合。spa
所謂透明的給一個對象增長功能,換句話說就是要給一個對象增長功能,可是不能讓這個對象知道,也就是不能去改動這個對象。而實現了可以給一個對象透明的增長功能,天然就可以實現功能的動態組合,好比原來的對象有A功能,如今透明的給它增長了一個B功能,是否是就至關於動態組合了A和B功能呢。.net
要想實現透明的給一個對象增長功能,也就是要擴展對象的功能了,使用繼承啊,有人立刻提出了一個方案,但很快就被否決了,那要減小或者修改功能呢?事實上繼承是很是不靈活的複用方式。那就用「對象組合」嘛,又有人提出新的方案來了,這個方案獲得了你們的贊同。component
在裝飾模式的實現中,爲了可以和原來使用被裝飾對象的代碼實現無縫結合,是經過定義一個抽象類,讓這個類實現與被裝飾對象相同的接口,而後在具體實現類裏面,轉調被裝飾的對象,在轉調的先後添加新的功能,這就實現了給被裝飾對象增長功能,這個思路跟「對象組合」很是相似。若是對「對象組合」不熟悉,請參見22.3.1的第2小節。htm
在轉調的時候,若是以爲被裝飾的對象的功能再也不須要了,還能夠直接替換掉,也就是再也不轉調,而是在裝飾對象裏面徹底全新的實現。對象
裝飾模式的結構如圖22.1所示:blog
圖22.1 裝飾模式結構圖繼承
Component:
組件對象的接口,能夠給這些對象動態的添加職責。
ConcreteComponent:
具體的組件對象,實現組件對象接口,一般就是被裝飾器裝飾的原始對象,也就是能夠給這個對象添加職責。
Decorator:
全部裝飾器的抽象父類,須要定義一個與組件接口一致的接口,並持有一個Component對象,其實就是持有一個被裝飾的對象。
注意這個被裝飾的對象不必定是最原始的那個對象了,也多是被其它裝飾器裝飾事後的對象,反正都是實現的同一個接口,也就是同一類型。
ConcreteDecorator:
實際的裝飾器對象,實現具體要向被裝飾對象添加的功能。
(1)先來看看組件對象的接口定義,示例代碼以下:
/** * 組件對象的接口,能夠給這些對象動態的添加職責 */ public abstract class Component { /** * 示例方法 */ public abstract void operation(); } |
(2)定義了接口,那就看看具體組件實現對象示意吧,示例代碼以下:
/** * 具體實現組件對象接口的對象 */ public class ConcreteComponent extends Component { public void operation() { //相應的功能處理 } } |
(3)接下來看看抽象的裝飾器對象,示例代碼以下:
/** * 裝飾器接口,維持一個指向組件對象的接口對象,並定義一個與組件接口一致的接口 */ public abstract class Decorator extends Component { /** * 持有組件對象 */ protected Component component; /** * 構造方法,傳入組件對象 * @param component 組件對象 */ public Decorator(Component component) { this.component = component; } public void operation() { //轉發請求給組件對象,能夠在轉發先後執行一些附加動做 component.operation(); } } |
(4)該來看看具體的裝飾器實現對象了,這裏有兩個示意對象,一個示意了添加狀態,一個示意了添加職責。先看添加了狀態的示意對象吧,示例代碼以下:
/** * 裝飾器的具體實現對象,向組件對象添加職責 */ public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } /** * 添加的狀態 */ private String addedState; public String getAddedState() { return addedState; } public void setAddedState(String addedState) { this.addedState = addedState; } public void operation() { //調用父類的方法,能夠在調用先後執行一些附加動做 //在這裏進行處理的時候,可使用添加的狀態 super.operation(); } } |
接下來看看添加職責的示意對象,示例代碼以下:
/** * 裝飾器的具體實現對象,向組件對象添加職責 */ public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } /** * 須要添加的職責 */ private void addedBehavior() { //須要添加的職責實現 } public void operation() { //調用父類的方法,能夠在調用先後執行一些附加動做 super.operation(); addedBehavior(); } } |
看完了裝飾模式的基本知識,該來考慮如何使用裝飾模式重寫前面的示例了。要使用裝飾模式來重寫前面的示例,大體會有以下改變:
首先須要定義一個組件對象的接口,在這個接口裏面定義計算獎金的業務方法,由於外部就是使用這個接口來操做裝飾模式構成的對象結構中的對象
須要添加一個基本的實現組件接口的對象,可讓它返回獎金爲0就能夠了
把各個計算獎金的規則看成裝飾器對象,須要爲它們定義一個統一的抽象的裝飾器對象,好約束各個具體的裝飾器的接口
把各個計算獎金的規則實現成爲具體的裝飾器對象
先看看如今示例的總體結構,好總體理解和把握示例,如圖22.2所示:
圖22.2 使用裝飾模式重寫示例的程序結構示意圖
(1)計算獎金的組件接口和基本的實現對象
在計算獎金的組件接口中,須要定義本來的業務方法,也就是實現獎金計算的方法,示例代碼以下:
/** * 計算獎金的組件接口 */ public abstract class Component { /** * 計算某人在某段時間內的獎金,有些參數在演示中並不會使用, * 可是在實際業務實現上是會用的,爲了表示這是個具體的業務方法, * 所以這些參數被保留了 * @param user 被計算獎金的人員 * @param begin 計算獎金的開始時間 * @param end 計算獎金的結束時間 * @return 某人在某段時間內的獎金 */ public abstract double calcPrize(String user ,Date begin,Date end); } |
爲這個業務接口提供一個基本的實現,示例代碼以下:
/** * 基本的實現計算獎金的類,也是被裝飾器裝飾的對象 */ public class ConcreteComponent extends Component{ public double calcPrize(String user, Date begin, Date end) { //只是一個默認的實現,默認沒有獎金 return 0; } } |
(2)定義抽象的裝飾器
在進一步定義裝飾器以前,先定義出各個裝飾器公共的父類,在這裏定義全部裝飾器對象須要實現的方法。這個父類應該實現組件的接口,這樣才能保證裝飾後的對象仍然能夠繼續被裝飾。示例代碼以下:
/** * 裝飾器的接口,須要跟被裝飾的對象實現一樣的接口 */ public abstract class Decorator extends Component{ /** * 持有被裝飾的組件對象 */ protected Component c; /** * 經過構造方法傳入被裝飾的對象 * @param c被裝飾的對象 */ public Decorator(Component c){ this.c = c; } public double calcPrize(String user, Date begin, Date end) { //轉調組件對象的方法 return c.calcPrize(user, begin, end); } } |
(3)定義一系列的裝飾器對象
用一個具體的裝飾器對象,來實現一條計算獎金的規則,如今有三條計算獎金的規則,那就對應有三個裝飾器對象來實現,依次來看看它們的實現。
這些裝飾器涉及到的TempDB跟之前同樣,這裏就不去贅述了。
首先來看實現計算當月業務獎金的裝飾器,示例代碼以下:
/** * 裝飾器對象,計算當月業務獎金 */ public class MonthPrizeDecorator extends Decorator{ public MonthPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先獲取前面運算出來的獎金 double money = super.calcPrize(user, begin, end); //2:而後計算當月業務獎金,按人員和時間去獲取當月業務額,而後再乘以3% double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03; System.out.println(user+"當月業務獎金"+prize); return money + prize; } } |
接下來看實現計算累計獎金的裝飾器,示例代碼以下:
/** * 裝飾器對象,計算累計獎金 */ public class SumPrizeDecorator extends Decorator{ public SumPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先獲取前面運算出來的獎金 double money = super.calcPrize(user, begin, end); //2:而後計算累計獎金,其實應按人員去獲取累計的業務額,而後再乘以0.1% //簡單演示一下,假定你們的累計業務額都是1000000元 double prize = 1000000 * 0.001; System.out.println(user+"累計獎金"+prize); return money + prize; } } |
接下來看實現計算當月團隊業務獎金的裝飾器,示例代碼以下:
/** * 裝飾器對象,計算當月團隊業務獎金 */ public class GroupPrizeDecorator extends Decorator{ public GroupPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先獲取前面運算出來的獎金 double money = super.calcPrize(user, begin, end); //2:而後計算當月團隊業務獎金,先計算出團隊總的業務額,而後再乘以1% //假設都是一個團隊的 double group = 0.0; for(double d : TempDB.mapMonthSaleMoney.values()){ group += d; } double prize = group * 0.01; System.out.println(user+"當月團隊業務獎金"+prize); return money + prize; } } |
(4)使用裝飾器的客戶端
使用裝飾器的客戶端,首先須要建立被裝飾的對象,而後建立須要的裝飾對象,接下來重要的工做就是組合裝飾對象,依次對前面的對象進行裝飾。
有不少相似的例子,好比生活中的裝修,就拿裝飾牆壁來講吧:沒有裝飾前是原始的磚牆,這就比如是被裝飾的對象,首先須要刷膩子,把牆找平,這就比如對原始的磚牆進行了一次裝飾,而刷的膩子就比如是一個裝飾器對象;好了,裝飾一回了,接下來該刷牆面漆了,這又比如裝飾了一回,刷的牆面漆就比如是又一個裝飾器對象,並且這回被裝飾的對象不是原始的磚牆了,而是被膩子裝飾器裝飾事後的牆面,也就是說後面的裝飾器是在前面的裝飾器裝飾事後的基礎之上,繼續裝飾的,相似於一層一層疊加的功能。
一樣的道理,計算獎金也是這樣,先建立基本的獎金對象,而後組合須要計算的獎金類型,依次組合計算,最後的結果就是總的獎金。示例代碼以下:
/** * 使用裝飾模式的客戶端 */ public class Client { public static void main(String[] args) { //先建立計算基本獎金的類,這也是被裝飾的對象 Component c1 = new ConcreteComponent();
//而後對計算的基本獎金進行裝飾,這裏要組合各個裝飾 //說明,各個裝飾者之間最好是不要有前後順序的限制, //也就是先裝飾誰和後裝飾誰都應該是同樣的
//先組合普通業務人員的獎金計算 Decorator d1 = new MonthPrizeDecorator(c1); Decorator d2 = new SumPrizeDecorator(d1);
//注意:這裏只需使用最後組合好的對象調用業務方法便可,會依次調用回去 //日期對象都沒有用上,因此傳null就能夠了 double zs = d2.calcPrize("張三",null,null); System.out.println("==========張三應得獎金:"+zs); double ls = d2.calcPrize("李四",null,null); System.out.println("==========李四應得獎金:"+ls);
//若是是業務經理,還須要一個計算團隊的獎金計算 Decorator d3 = new GroupPrizeDecorator(d2); double ww = d3.calcPrize("王五",null,null); System.out.println("==========王經理應得獎金:"+ww); } } |
測試一下,看看結果,示例以下:
張三當月業務獎金300.0 張三累計獎金1000.0 ==========張三應得獎金:1300.0 李四當月業務獎金600.0 李四累計獎金1000.0 ==========李四應得獎金:1600.0 王五當月業務獎金900.0 王五累計獎金1000.0 王五當月團隊業務獎金600.0 ==========王經理應得獎金:2500.0 |
當測試運行的時候會按照裝飾器的組合順序,依次調用相應的裝飾器來執行業務功能,是一個遞歸的調用方法,以業務經理「王五」的獎金計算作例子,畫個圖來講明獎金的計算過程吧,看看是如何調用的,如圖22.3所示:
圖22.3 裝飾模式示例的組合和調用過程示意圖
這個圖很好的揭示了裝飾模式的組合和調用過程,請仔細體會一下。
如同上面的示例,對於基本的計算獎金的對象而言,因爲計算獎金的邏輯太過於複雜,並且須要在不一樣的狀況下進行不一樣的運算,爲了靈活性,把多種計算獎金的方式分散到不一樣的裝飾器對象裏面,採用動態組合的方式,來給基本的計算獎金的對象增添計算獎金的功能,每一個裝飾器至關於計算獎金的一個部分。
這種方式明顯比爲基本的計算獎金的對象增長子類來得更靈活,由於裝飾模式的起源點是採用對象組合的方式,而後在組合的時候順便增長些功能。爲了達到一層一層組裝的效果,裝飾模式還要求裝飾器要實現與被裝飾對象相同的業務接口,這樣才能以同一種方式依次組合下去。
靈活性還體如今動態上,若是是繼承的方式,那麼全部的類實例都有這個功能了,而採用裝飾模式,能夠動態的爲某幾個對象實例添加功能,而不是對整個類添加功能。好比上面示例中,客戶端測試的時候,對張三李四就只是組合了兩個功能,對王五就組合了三個功能,可是原始的計算獎金的類都是同樣的,只是動態的爲它增長的功能不一樣而已。
原創內容,轉載請註明出處【http://sishuok.com/forum/blogPost/list/0/5667.html】