結合案例深刻解析裝飾者模式

1、基本概念

裝飾者模式是結構型設計模式。java

裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案。git

容許向一個現有的對象添加新的功能。同時又不改變其結構,它是做爲現有的類的一個包裝。github

主要解決的問題: 通常咱們爲了擴展一個類常常使用繼承方式實現,因爲繼承爲類引入靜態特徵,而且隨着擴展功能的增多,子類會很膨脹設計模式

2、結構

結構:數組

  • 裝飾者(Decorator)和具體組件(ConcreteComponent)都繼承自組件(Component);
  • 所謂裝飾,就是把這個裝飾者套在被裝飾者之上,從而動態擴展被裝飾者的功能;
  • 裝飾者的方法有一部分是本身的,這屬於它的功能(半透明的裝飾者模式)。而後調用被裝飾者的方法實現,從而也保留了被裝飾者的功能;

03_decorator_02.png

3、案例

一、裝飾者模式案例

模擬在餐館點飲料,咱們能夠點咖啡,而咖啡有Decaf咖啡和Espresso咖啡,而這兩種咖啡均可以加牛奶和巧克力進去。bash

具體的代碼組織結構圖:ide

03_decorator_01.png

具體代碼:函數

先看最高的component包下的Drink類:性能

/**
 * Component的超類
 * 單品和裝飾者都要繼承自這個類
 */
public abstract class Drink {

    private String description = ""; //一開始沒有描述
    private double price = 0; //一開始價格爲0

    /**
     * 抽象方法
     *  一、若是是單品的話就直接是本身的價格
     *  二、若是是裝飾者的話就還要加上裝飾品本身的價格
     */
    public abstract double cost();


    // setter getter

    public String getDescription() {
        return description;
    }
    public double getPrice() {
        return price;
    }
    public void setDescription(String description) { //描述的時候順便把價格描述一下
        this.description = description;
    }
    public void setPrice(double price) {
        this.price = price;
    }
}複製代碼

下面看兩個具體的Component:測試

/** ConcreteComponent 1*/
public class Decaf extends Drink {

    public Decaf() {
        super.setDescription("Decaf");
        super.setPrice(3); //3塊錢
    }

    @Override
    public double cost() {
        return getPrice();//super.getPrice()//這個就是父類的價格(本身什麼也沒加 (沒有被裝飾))
    }

    // 重寫getter 後面加上本身的花費
    @Override
    public String getDescription() {
        return super.getDescription() + "-" + cost();
    }
}
複製代碼
/** ConcreteComponent 2
 *  也能夠在ConcreteComponent和Drink類有一個過渡的類)  (好比Coffee類)
 */
public class Espresso extends Drink {

    public Espresso(){
        super.setDescription("Espresso");
        super.setPrice(4);
    }

    @Override
    public double cost() {
        return getPrice();//super.getPrice()//這個就是父類的價格(本身什麼也沒加)
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + cost();
    }
}複製代碼

下面看decorator下的三個類:

第一個是裝飾者的超類,繼承自Drink類:

public class Decorator extends Drink{
    /**
     * 這個引用很重要,能夠是單品,也能夠是被包裝過的類型,因此使用的是超類的對象
     * 這個就是要被包裝的單品(被裝飾的對象)
     */
    private Drink drink; //這裏要拿到父類的引用,由於要控制另外一個分支(具體的組件)

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    /**
     * 若是drink是已經被裝包過的,那麼就會產生遞歸調用  最終到單品
     */
    @Override
    public double cost() {
        return super.getPrice() + drink.cost(); // 本身的價格和被包裝單品的價格
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + super.getPrice()
                + " && " + drink.getDescription();
    }
}複製代碼

而後是兩個裝飾者:

/**
 * 這個是具體的裝飾者() --> 繼承自中間的裝飾着Decorator
 */
public class Chocolate extends Decorator{

    public Chocolate(Drink drink) { //若是父類搞了一個 帶參數的構造函數,子類必須顯示的使用super調用
        super(drink);
        super.setDescription("Chocolate");
        super.setPrice(1);
    }
}複製代碼
public class Milk extends Decorator{

    public Milk(Drink drink) {
        super(drink); //調用父類的構造函數
        super.setDescription("Milk");
        super.setPrice(3);
    }
}複製代碼

測試類:

public class MyTest {
    public static void main(String[] args) {
        //只點一個單品 (Decaf 咖啡)
        Drink order = new Decaf();
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());

        System.out.println("---------------加了調料的----------------");

        order = new Milk(order);// 加了牛奶
        order = new Chocolate(order);
        order = new Chocolate(order); // 加了兩個巧克力
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());
    }
}複製代碼

程序輸出:

order description : Decaf-3.0
order price : 3.0
---------------加了調料的----------------
order description : Chocolate-1.0 && Chocolate-1.0 && Milk-3.0 && Decaf-3.0
order price : 8.0複製代碼

二、JavaIO中使用裝飾者模式

因爲Java I/O庫須要不少性能的各類組合,若是這些性能都是用繼承的方法實現的,那麼每一種組合都須要一個類,這樣就會形成大量性能重複的類出現,因此Java IO使用的是裝飾者設計模式。

因此咱們能夠定義本身的裝飾者。

這裏咱們定義一個流,這個流將讀入的小寫字母轉換成大寫字母。

UpperCaseInputStream代碼以下:

/**
 * 本身定義的輸入流  
 * 擴展FilterInputStream(這個類就是咱們的Decorator) 中間裝飾者  
 * 因此咱們只要繼承這個就能夠擴展本身的輸入流裝飾者 
 */
public class UpperCaseInputStream extends FilterInputStream{

    protected UpperCaseInputStream(InputStream in) {  //這個InputStream就是咱們的Drink 類(超類)
        super(in);
    }

    // 實現兩個read()方法,將大寫轉化成小寫的讀入

    //重寫 至關於cost和description
    @Override
    public int read() throws IOException {
        int index = super.read(); //讀取一個字節
        return index == -1 ? index : Character.toUpperCase((char)(index));  //小寫轉換成大寫
    }

    //字節數組
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int index = super.read(b, off, len);
        for(int i = 0; i < index; i++)
            b[i] = (byte)Character.toUpperCase((char)(b[i]));
        return index;
    }
}複製代碼

測試一下使用這個類:

public class MyTest {

    public static void main(String[] args) throws IOException {
        InputStream in = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/decorator/java/in.txt")));// 將這個in.txt文件讀入的內容轉換成大寫
        int len;
        while((len = in.read()) >= 0)
            System.out.print((char)(len));
        in.close();
    }
}複製代碼

輸出結果演示:

03_decorator_04.png

4、總結

優缺點:

  • 優勢 : 裝飾類和被裝飾類能夠獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式能夠動態擴展一個實現類的功能。
  • 缺點 : 多層裝飾比較複雜。

實際應用:  大多數狀況下,裝飾模式的實現都要比上面給出的示意性例子要簡單。

  • 若是隻有一個ConcreteComponent類,那麼能夠考慮去掉抽象的Component類(接口),把Decorator做爲一個ConcreteComponent子類;
  •  若是隻有一個ConcreteDecorator類,那麼就沒有必要創建一個單獨的Decorator類,而能夠把Decorator和ConcreteDecorator的責任合併成一個類。
相關文章
相關標籤/搜索