裝飾模式

裝飾模式(Decorator)

1  場景問題

1.1  複雜的獎金計算

        考慮這樣一個實際應用:就是如何實現靈活的獎金計算。
        獎金計算是相對複雜的功能,尤爲是對於業務部門的獎金計算方式,是很是複雜的,除了業務功能複雜外,另一個麻煩之處是計算方式還常常須要變更,由於業務部門常常經過調整獎金的計算方式來激勵士氣。
        先從業務上看看現有的獎金計算方式的複雜性:html

  • 首先是獎金分類:對於我的,大體有我的當月業務獎金、我的累計獎金、我的業務增加獎金、及時回款獎金、限時成交加碼獎金等等;java

  • 對於業務主管或者是業務經理,除了我的獎金外,還有:團隊累計獎金、團隊業務增加獎金、團隊盈利獎金等等。算法

  • 其次是計算獎金的金額,又有這麼幾個基數:銷售額、銷售毛利、實際回款、業務成本、獎金基數等等;數據庫

  • 另一個就是計算的公式,針對不一樣的人、不一樣的獎金類別、不一樣的計算獎金的金額,計算的公式是不一樣的,就算是同一個公式,裏面計算的比例參數也有多是不一樣的。設計模式

 

1.2  簡化後的獎金計算體系

        看了上面獎金計算的問題,所幸咱們只是來學習設計模式,並非真的要去實現整個獎金計算體系的業務,所以也沒有必要把全部的計算業務都羅列在這裏,爲了後面演示的須要,簡化一下,演示用的獎金計算體系以下:app

  • 每一個人當月業務獎金 = 當月銷售額 X  3%ide

  • 每一個人累計獎金 = 總的回款額 X  0.1%學習

  • 團隊獎金 = 團隊總銷售額 X 1%測試

 

1.3  不用模式的解決方案

        一我的的獎金分紅不少個部分,要實現獎金計算,主要就是要按照各個獎金計算的規則,把這我的能夠獲取的每部分獎金計算出來,而後計算一個總和,這就是這我的能夠獲得的獎金。
(1)爲了演示,先準備點測試數據,在內存中模擬數據庫,示例代碼以下:this

/** 
 * 在內存中模擬數據庫,準備點測試數據,好計算獎金 
 */  
public class TempDB {  
    private TempDB(){  
}  
    /** 
     * 記錄每一個人的月度銷售額,只用了人員,月份沒有用 
     */  
    public static Map<String,Double> mapMonthSaleMoney =   
new HashMap<String,Double>();  
    static{  
        //填充測試數據  
        mapMonthSaleMoney.put("張三",10000.0);  
        mapMonthSaleMoney.put("李四",20000.0);  
        mapMonthSaleMoney.put("王五",30000.0);  
    }  
}

(2)按照獎金計算的規則,實現獎金計算,示例代碼以下:

/** 
 * 計算獎金的對象 
 */  
public class Prize {  
    /** 
     * 計算某人在某段時間內的獎金,有些參數在演示中並不會使用, 
     * 可是在實際業務實現上是會用的,爲了表示這是個具體的業務方法, 
     * 所以這些參數被保留了 
     * @param user 被計算獎金的人員 
     * @param begin 計算獎金的開始時間 
     * @param end 計算獎金的結束時間 
     * @return 某人在某段時間內的獎金 
     */  
    public  double calcPrize(String user,Date begin,Date end){  
        double prize = 0.0;   
        //計算當月業務獎金,全部人都會計算  
        prize = this.monthPrize(user, begin, end);  
        //計算累計獎金  
        prize += this.sumPrize(user, begin, end);  
          
        //須要判斷該人員是普通人員仍是業務經理,團隊獎金只有業務經理纔有  
        if(this.isManager(user)){  
            prize += this.groupPrize(user, begin, end);  
        }  
        return prize;  
    }  
  
    /** 
     * 計算某人的當月業務獎金,參數重複,就再也不註釋了 
     */  
    private double monthPrize(String user, Date begin, Date end) {  
        //計算當月業務獎金,按照人員去獲取當月的業務額,而後再乘以3%  
        double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03;  
        System.out.println(user+"當月業務獎金"+prize);  
        return prize;  
    }  
  
    /** 
     * 計算某人的累計獎金,參數重複,就再也不註釋了 
     */  
    public double sumPrize(String user, Date begin, Date end) {  
        //計算累計獎金,其實應該按照人員去獲取累計的業務額,而後再乘以0.1%  
        //簡單演示一下,假定你們的累計業務額都是1000000元  
        double prize = 1000000 * 0.001;  
        System.out.println(user+"累計獎金"+prize);  
        return prize;  
    }     
  
    /** 
     * 判斷人員是普通人員仍是業務經理 
     * @param user 被判斷的人員 
     * @return true表示是業務經理,false表示是普通人員 
     */  
    private boolean isManager(String user){  
        //應該從數據庫中獲取人員對應的職務  
        //爲了演示,簡單點判斷,只有王五是經理  
        if("王五".equals(user)){  
            return true;              
        }  
        return false;  
    }  
    /** 
     * 計算當月團隊業務獎,參數重複,就再也不註釋了 
     */  
    public double groupPrize(String user, Date begin, Date end) {  
        //計算當月團隊業務獎金,先計算出團隊總的業務額,而後再乘以1%,  
//假設都是一個團隊的  
        double group = 0.0;  
        for(double d : TempDB.mapMonthSaleMoney.values()){  
            group += d;  
        }  
        double prize = group * 0.01;  
        System.out.println(user+"當月團隊業務獎金"+prize);  
        return prize;  
    }  
}

(3)寫個客戶端來測試一下,看看是否能正確地計算獎金,示例代碼以下:

public class Client {  
    public static void main(String[] args) {  
        //先建立計算獎金的對象  
        Prize p = new Prize();  
          
        //日期對象都沒有用上,因此傳null就能夠了  
        double zs = p.calcPrize("張三",null,null);          
        System.out.println("==========張三應得獎金:"+zs);  
        double ls = p.calcPrize("李四",null,null);  
        System.out.println("==========李四應得獎金:"+ls);       
        double ww = p.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

2.1裝飾模式來解決

(1)裝飾模式定義

  動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式比生成子類更爲靈活。

(2)應用裝飾模式來解決的思路
        雖然通過簡化,業務簡單了不少,可是須要解決的問題不會少,仍是要解決:要透明的給一個對象增長功能,並實現功能的動態組合。
        所謂透明的給一個對象增長功能,換句話說就是要給一個對象增長功能,可是不能讓這個對象知道,也就是不能去改動這個對象。而實現了可以給一個對象透明的增長功能,天然就可以實現功能的動態組合,好比原來的對象有A功能,如今透明的給它增長了一個B功能,是否是就至關於動態組合了A和B功能呢。
        要想實現透明的給一個對象增長功能,也就是要擴展對象的功能了,使用繼承啊,有人立刻提出了一個方案,但很快就被否決了,那要減小或者修改功能呢?事實上繼承是很是不靈活的複用方式。那就用「對象組合」嘛,又有人提出新的方案來了,這個方案獲得了你們的贊同。
        在裝飾模式的實現中,爲了可以和原來使用被裝飾對象的代碼實現無縫結合,是經過定義一個抽象類,讓這個類實現與被裝飾對象相同的接口,而後在具體實現類裏面,轉調被裝飾的對象,在轉調的先後添加新的功能,這就實現了給被裝飾對象增長功能,這個思路跟「對象組合」很是相似。若是對「對象組合」不熟悉,請參見3.1的第2小節。
在轉調的時候,若是以爲被裝飾的對象的功能再也不須要了,還能夠直接替換掉,也就是再也不轉調,而是在裝飾對象裏面徹底全新的實現。

2.2  模式結構和說明

wKioL1ljOPzzAC_xAACIWxBEs1M805.png

Component:
        組件對象的接口,能夠給這些對象動態的添加職責。
ConcreteComponent:
        具體的組件對象,實現組件對象接口,一般就是被裝飾器裝飾的原始對象,也就是能夠給這個對象添加職責。
Decorator:
        全部裝飾器的抽象父類,須要定義一個與組件接口一致的接口,並持有一個Component對象,其實就是持有一個被裝飾的對象。
        注意這個被裝飾的對象不必定是最原始的那個對象了,也多是被其它裝飾器裝飾事後的對象,反正都是實現的同一個接口,也就是同一類型。
ConcreteDecorator:
        實際的裝飾器對象,實現具體要向被裝飾對象添加的功能。

2.3  裝飾模式示例代碼

(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();  
    }  
}

2.4  使用裝飾模式示例

演示用的獎金計算體系以下:

  • 每一個人當月業務獎金 = 當月銷售額 X  3%

  • 每一個人累計獎金 = 總的回款額 X  0.1%

  • 團隊獎金 = 團隊總銷售額 X 1%

 大體內容以下:

  • 首先須要定義一個組件對象的接口,在這個接口裏面定義計算獎金的業務方法,由於外部就是使用這個接口來操做裝飾模式構成的對象結構中的對象

  • 須要添加一個基本的實現組件接口的對象,可讓它返回獎金爲0就能夠了

  • 把各個計算獎金的規則看成裝飾器對象,須要爲它們定義一個統一的抽象的裝飾器對象,好約束各個具體的裝飾器的接口

  • 把各個計算獎金的規則實現成爲具體的裝飾器對象

先看看如今示例的總體結構,好總體理解和把握示例,如圖所示:

wKioL1ljPICwwYwBAABXWygwBJg029.png

(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

當測試運行的時候會按照裝飾器的組合順序,依次調用相應的裝飾器來執行業務功能,是一個遞歸的調用方法,以業務經理「王五」的獎金計算作例子,畫個圖來講明獎金的計算過程吧,看看是如何調用的,如圖所示

wKioL1ljPrLiUO6VAAAxBi4Ha5U703.png

如同上面的示例,對於基本的計算獎金的對象而言,因爲計算獎金的邏輯太過於複雜,並且須要在不一樣的狀況下進行不一樣的運算,爲了靈活性,把多種計算獎金的方式分散到不一樣的裝飾器對象裏面,採用動態組合的方式,來給基本的計算獎金的對象增添計算獎金的功能,每一個裝飾器至關於計算獎金的一個部分。
       這種方式明顯比爲基本的計算獎金的對象增長子類來得更靈活,由於裝飾模式的起源點是採用對象組合的方式,而後在組合的時候順便增長些功能。爲了達到一層一層組裝的效果,裝飾模式還要求裝飾器要實現與被裝飾對象相同的業務接口,這樣才能以同一種方式依次組合下去。
       靈活性還體如今動態上,若是是繼承的方式,那麼全部的類實例都有這個功能了,而採用裝飾模式,能夠動態的爲某幾個對象實例添加功能,而不是對整個類添加功能。好比上面示例中,客戶端測試的時候,對張三李四就只是組合了兩個功能,對王五就組合了三個功能,可是原始的計算獎金的類都是同樣的,只是動態的爲它增長的功能不一樣而已。

3.1  Java中的裝飾模式應用

1:本身實現的I/O流的裝飾器

        要讓咱們寫的裝飾器跟其它Java中的裝飾器同樣用,最合理的方案就應該是:讓咱們的裝飾器繼承裝飾器的父類,也就是FilterOutputStream類,而後使用父類提供的功能來協助完成想要裝飾的功能。示例代碼以下:

public class EncryptOutputStream2  extends FilterOutputStream{
    private OutputStream os = null;
    public EncryptOutputStream2(OutputStream os){
       //調用父類的構造方法
       super(os);
    }
    public void write(int a) throws IOException {
       //先統一貫後移動兩位
       a = a+2;
       //97是小寫的a的碼值
       if(a >= (97+26)){
           //若是大於,表示已是y或者z了,減去26就回到a或者b了
           a = a-26;
       }
       //調用父類的方法
       super.write(a);
    }
}

測試一下看看,好用嗎?客戶端使用代碼示例以下:

public class Client {
	public static void main(String[] args) throws Exception {
		//流式輸出文件
		DataOutputStream dout = new DataOutputStream(
			new EncryptOutputStream (
				new BufferedOutputStream(
					new FileOutputStream("MyEncrypt.txt"))));
		dout.write("abcdxyz".getBytes());
		dout.close();
	}
}

運行一下,打開生成的文件,看看結果,結果示例以下:

cdefzab

3.2使用裝飾模式作出相似AOP的效果
        下面來演示一下使用裝飾模式,把一些公共的功能,好比權限控制,日誌記錄,透明的添加回到業務功能模塊中去,作出相似AOP的效果。
(1)首先定義業務接口
        這個接口至關於裝飾模式的Component。注意這裏使用的是接口,而不像前面同樣使用的是抽象類,雖然使用抽象類的方式來定義組件是裝飾模式的標準實現方式,可是若是不須要爲子類提供公共的功能的話,也是能夠實現成接口的,這點要先說明一下,省得有些朋友會認爲這就不是裝飾模式了,示例代碼以下:

/** 
* 商品銷售管理的業務接口 
*/  
public interface GoodsSaleEbi {  
    /** 
     * 保存銷售信息,原本銷售數據應該是多條,太麻煩了,爲了演示,簡單點 
     * @param user 操做人員 
     * @param customer 客戶 
     * @param saleModel 銷售數據 
     * @return 是否保存成功 
     */  
    public boolean sale(String user,String customer,  
SaleModel saleModel);  
}

順便把封裝業務數據的對象也定義出來,很簡單,示例代碼以下:

/** 
* 封裝銷售單的數據,簡單的示意一些 
*/  
public class SaleModel {  
    /** 
    * 銷售的商品 
    */  
    private String goods;  
    /** 
    * 銷售的數量 
    */  
    private int saleNum;  
    public String getGoods() {    
        return goods;     
    }  
    public void setGoods(String goods) {  
        this.goods = goods;   
    }  
    public int getSaleNum() {  
        return saleNum;  
    }  
    public void setSaleNum(int saleNum) {  
        this.saleNum = saleNum;  
    }  
    public String toString(){  
        return "商品名稱="+goods+",購買數量="+saleNum;  
    }  
}

(2)定義基本的業務實現對象,示例代碼以下:

public class GoodsSaleEbo implements GoodsSaleEbi{  
    public boolean sale(String user,String customer,   
                      SaleModel saleModel) {  
        System.out.println(user+"保存了"  
                                 +customer+"購買 "+saleModel+" 的銷售數據");  
        return true;  
    }  
}

(3)接下來該來實現公共功能了,把這些公共功能實現成爲裝飾器,那麼須要給它們定義一個抽象的父類,示例以下:

/** 
* 裝飾器的接口,須要跟被裝飾的對象實現一樣的接口 
*/  
public abstract class Decorator implements GoodsSaleEbi{  
    /** 
               * 持有被裝飾的組件對象 
           */  
    protected GoodsSaleEbi ebi;  
    /** 
     * 經過構造方法傳入被裝飾的對象 
     * @param ebi被裝飾的對象 
     */  
    public Decorator(GoodsSaleEbi ebi){  
        this.ebi = ebi;  
    }  
}

(4)實現權限控制的裝飾器
先檢查是否有運行的權限,若是有就繼續調用,若是沒有,就不遞歸調用了,而是輸出沒有權限的提示,示例代碼以下:

/** 
 * 實現權限控制 
 */  
public class CheckDecorator extends Decorator{  
    public CheckDecorator(GoodsSaleEbi ebi){  
        super(ebi);  
    }  
    public boolean sale(String user,String customer  
        , SaleModel saleModel) {  
        //簡單點,只讓張三執行這個功能  
        if(!"張三".equals(user)){  
            System.out.println("對不起"+user  
                +",你沒有保存銷售單的權限");  
            //就再也不調用被裝飾對象的功能了  
            return false;  
        }else{  
            return this.ebi.sale(user,customer,saleModel);  
        }         
    }  
}

(5)實現日誌記錄的裝飾器,就是在功能執行完成後記錄日誌便可,示例代碼以下:

/** 
* 實現日誌記錄 
*/  
public class LogDecorator extends Decorator{  
    public LogDecorator(GoodsSaleEbi ebi){  
        super(ebi);  
    }  
    public boolean sale(String user,String customer,   
        SaleModel saleModel) {  
        //執行業務功能  
        boolean f = this.ebi.sale(user, customer, saleModel);  
  
        //在執行業務功能事後,記錄日誌  
        DateFormat df =   
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");  
        System.out.println("日誌記錄:"+user+"於"+  
                df.format(new Date())+"時保存了一條銷售記錄,客戶是"  
                +customer+",購買記錄是"+saleModel);  
        return f;  
    }  
}

(6)組合使用這些裝飾器
        在組合的時候,權限控制應該是最早被執行的,因此把它組合在最外面,日誌記錄的裝飾器會先調用原始的業務對象,因此把日誌記錄的裝飾器組合在中間。
        前面講過,裝飾器之間最好不要有順序限制,可是在實際應用中,要根據具體的功能要求來,有須要的時候,也能夠有順序的限制,但應該儘可能避免這種狀況。
        此時客戶端測試代碼示例以下:

public class Client {  
    public static void main(String[] args) {  
        //獲得業務接口,組合裝飾器  
        GoodsSaleEbi ebi = new CheckDecorator(  
                new LogDecorator(  
                new GoodsSaleEbo()));  
        //準備測試數據  
        SaleModel saleModel = new SaleModel();  
        saleModel.setGoods("Moto手機");  
        saleModel.setSaleNum(2);  
        //調用業務功能  
        ebi.sale("張三","張三丰", saleModel);  
        ebi.sale("李四","張三丰", saleModel);  
    }  
}

運行結果以下:

wKiom1ljg6XzQDfWAAFsnPDv6ls005.png

好好體會一下,是否是也在沒有驚動原始業務對象的狀況下,給它織入了新的功能呢?也就是說是在原始業務不知情的狀況下,給原始業務對象透明的增長了新功能,從而模擬實現了AOP的功能。
        事實上,這種作法,徹底能夠應用在項目開發上,在後期爲項目的業務對象添加數據檢查、權限控制、日誌記錄等功能,就不須要在業務對象上去處理這些功能了,業務對象能夠更專一於具體業務的處理。

3.3  裝飾模式的優缺點

  • 比繼承更靈活
        從爲對象添加功能的角度來看,裝飾模式比繼承來得更靈活。繼承是靜態的,並且一旦繼承是全部子類都有同樣的功能。而裝飾模式採用把功能分離到每一個裝飾器當中,而後經過對象組合的方式,在運行時動態的組合功能,每一個被裝飾的對象,最終有哪些功能,是由運行期動態組合的功能來決定的。

  • 更容易複用功能
        裝飾模式把一系列複雜的功能,分散到每一個裝飾器當中,通常一個裝飾器只實現一個功能,這樣實現裝飾器變得簡單,更重要的是這樣有利於裝飾器功能的複用,能夠給一個對象增長多個一樣的裝飾器,也能夠把一個裝飾器用來裝飾不一樣的對象,從而複用裝飾器的功能。

  • 簡化高層定義
        裝飾模式能夠經過組合裝飾器的方式,給對象增添任意多的功能,所以在進行高層定義的時候,不用把全部的功能都定義出來,而是定義最基本的就能夠了,能夠在使用須要的時候,組合相應的裝飾器來完成須要的功能。

  • 會產生不少細粒度對象
        前面說了,裝飾模式是把一系列複雜的功能,分散到每一個裝飾器當中,通常一個裝飾器只實現一個功能,這樣會產生不少細粒度的對象,並且功能越複雜,須要的細粒度對象越多。

3.4  相關模式

  • 裝飾模式與適配器模式
        這是兩個沒有什麼關聯的模式,放到一塊兒來講,是由於它們有一個共同的別名:Wrapper。
        這兩個模式功能上是不同的,適配器模式是用來改變接口的,而裝飾模式是用來改變對象功能的。

  • 裝飾模式與組合模式
        這兩個模式有類似之處,都涉及到對象的遞歸調用,從某個角度來講,能夠把裝飾當作是隻有一個組件的組合。
        可是它們的目的徹底不同,裝飾模式是要動態的給對象增長功能;而組合模式是想要管理組合對象和葉子對象,爲它們提供一個一致的操做接口給客戶端,方便客戶端的使用。

  • 裝飾模式與策略模式
        這兩個模式能夠組合使用。
        策略模式也能夠實現動態的改變對象的功能,可是策略模式只是一層選擇,也就是根據策略選擇一下具體的實現類而已。而裝飾模式不是一層,而是遞歸調用,無數層均可以,只要組合好裝飾器的對象組合,那就能夠依次調用下去,因此裝飾模式會更靈活。
        並且策略模式改變的是原始對象的功能,不像裝飾模式,後面一個裝飾器,改變的是通過前一個裝飾器裝飾事後的對象,也就是策略模式改變的是對象的內核,而裝飾模式改變的是對象的外殼。
        這兩個模式能夠組合使用,能夠在一個具體的裝飾器裏面使用策略模式,來選擇更具體的實現方式。好比前面計算獎金的另一個問題就是參與計算的基數不一樣,獎金的計算方式也是不一樣的。舉例來講:假設張三和李四參與同一個獎金的計算,張三的銷售總額是2萬元,而李四的銷售額是8萬元,它們的計算公式是不同的,假設獎金的計算規則是,銷售額在5萬如下,統一3%,而5萬以上,5萬內是4%,超過部分是6%。
        參與同一個獎金的計算,這就意味着可使用同一個裝飾器,可是在裝飾器的內部,不一樣條件下計算公式不同,那麼怎麼選擇具體的實現策略呢?天然使用策略模式就行了,也就是裝飾模式和策略模式組合來使用。

  • 裝飾模式與模板方法模式
        這是兩個功能上有類似點的模式。
        模板方法模式主要應用在算法骨架固定的狀況,那麼要是算法步驟不固定呢,也就是一個相對動態的算法步驟,就可使用裝飾模式了,由於在使用裝飾模式的時候,進行裝飾器的組裝,其實也至關因而一個調用算法步驟的組裝,至關因而一個動態的算法骨架。
        既然裝飾模式能夠實現動態的算法步驟的組裝和調用,那麼把這些算法步驟固定下來,那就是模板方法模式實現的功能了,所以裝飾模式能夠模擬實現模板方法模式的功能。
        可是請注意,僅僅只是能夠模擬功能而已,兩個模式的設計目的、本來的功能、本質思想等都是不同的。

轉載至:http://sishuok.com/forum/blogPost/list/113.html

   cc老師的設計模式是我目前看過最詳細最有實踐的教程。

相關文章
相關標籤/搜索