1. 程式人生 > >設計模式(三)裝飾者模式

設計模式(三)裝飾者模式

參考:

1. 概念解析

裝飾者模式:在不改變原類檔案和繼承的情況下,動態的拓展一個物件的功能,通過建立一個包裝物件,也就是裝飾來包裹真實的物件。 特點:

  1. 裝飾物件和真實物件有相同的介面,這樣客戶端物件就能用同樣的方式和裝飾物件互動(與真實物件互動也一樣的方式)。
  2. 裝飾物件包含一個真實物件的引用,這個引用就是為了下面的這一點,呼叫真實物件的方法。
  3. 裝飾物件接受所有來自客戶端的請求,把這些請求轉發給真實物件。
  4. 裝飾物件可以再轉發請求時,新增一些自己的附加功能,所以在執行時,可以不修改原真實物件,達到拓展功能的意圖。通常通過繼承來實現對指定類的功能拓展。 截個圖放在這裡,反正你也看不懂,先跳過往下看。 在這裡插入圖片描述
    headfirst這本書舉了這麼個例子,星巴克的咖啡訂單系統設計。當然不是整體,就拿飲料這塊來設計一下。 在這裡插入圖片描述 嗯,不同的飲料繼承實現超類的方法,得到各自的計算價格的方式。所以你得到了這樣一個結果 在這裡插入圖片描述 想想星巴克在那麼多國家,那麼多地區開了多少家店了,會有多少種風格各異的飲料。估計是要奔潰了。 這就要考慮使用另外一種設計模式。 重新設計一下咖啡 在這裡插入圖片描述 注意到這裡的CondimentDecorator拓展自Beverage類,用到了繼承,目的其實是利用繼承達到“型別匹配”,而不是利用繼承獲得“行為”。因為裝飾者模式,其中一點是,裝飾者必須能取代被裝飾者。 那麼所說的“行為”是從哪裡來的呢,這就是將裝飾者與元件組合時,加入的新行為,所得到的新行為不是繼承自超類,而是組合物件得到的。

2. 應用場景

  1. 需要拓展一個類的功能,或者給一個類新增附加職責
  2. 需要動態的給一個物件新增功能,並且可以動態的撤銷這些功能。
  3. 需要增加由一些基本的功能排列組合而產生的大量的功能。
  4. 不能採用生成子類的方法進行擴充功能時,或者是類的定義被隱藏,或者不能用於生成子類。 java.io這個包就是典型的裝飾者模式設計的。 在這裡插入圖片描述 再詳細一點的解釋: 在這裡插入圖片描述 BufferedInputStream及LineNumberInputStream都擴充套件自 FilterInputStream,而FilterInputStream是一個抽象的裝飾類。

不過裝飾者模式也有個缺點,就是會建立很多的小類,那麼在使用這些包裝類的時候可能會造成一定的困擾。

3. 模式組成

裝飾者由4個元素組成

  1. 抽象元件(Component),beverage,一般是一個抽象類或者介面,用來規範準備接受附加責任的物件
  2. 具體元件(ConcreteComponent),houseblend,實現了抽象元件的類,定義一個將要接收附加責任的類。
  3. 抽象裝飾者(Decorator),condimentdecorator,持有一個元件物件的例項,並實現一個與抽象元件一致的介面
  4. 具體裝飾者,mocha。新增附加責任。

4. 程式碼實現

來杯2杯咖啡,一個原味,一個加摩卡。。

  1. 基礎的飲料抽象元件:
public abstract class Beverage {
    public String description = "xxx";
    //列印一些文字資訊
    public String getDescription() {
        return description;
    }
    public abstract double cost();
}
  1. 具體的實現,在計算中直接返回咖啡的價格。
public class Espresso extends Beverage {
    public Espresso(){
        description = ".Espresso.";
    }
    @Override
    public double cost() {
        return 1.99;
    }
}
  1. 為了實現有摩卡的咖啡,我們不能修改咖啡的部分,而是把摩卡當成元件插入。那麼就來一個裝飾者。
public abstract class CondimentDecorator extends Beverage{
    @Override
    public abstract String getDescription();
}
  1. 實現這個裝飾者,並加上一些額外的功能。
public class Mocha extends CondimentDecorator {
    Beverage beverage;
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + "...mocha";
    }
    @Override
    public double cost() {
        return 1.89 + beverage.cost();
    }
}
  1. 測試
    @Test
    public void test(){
        Beverage b = new Espresso();
        System.out.println(b.getDescription() + " " + b.cost());
        Beverage b2 = new Espresso();
        b2 = new Mocha(b2);
        System.out.println(b2.getDescription() + " " + b2.cost());
    }
列印:
.Espresso. 1.99
.Espresso....mocha 3.88

看到加了mocha裝飾者,並沒有修改原咖啡類。

另外一個例子,新建一個自己的java io裝飾類,把讀取的檔案中所有的大寫字母變成小寫的。

public class LcInputStream extends FileInputStream {
    public LcInputStream(String name) throws FileNotFoundException {
        super(name);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char) c));
    }

    @Override
    public int read(byte[] b, int offset, int len) throws IOException {
        int result = super.read(b, offset, len);
        for (int i = offset; i < offset + result; i++) {
            b[i] = (byte) Character.toLowerCase((char) b[i]);
        }
        return result;
    }
}

測試程式碼:

try {
    int c;
    // 讀取檔案的地址
    InputStream in = new LcInputStream("/Users/xx/Downloads/fee.html");
    in.read();
    while ((c = in.read()) >= 0) {
        System.out.print((char) c);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
列印:讀取的是html檔案,有大小寫的,得到的結果都是小寫

5. 優缺點

優點,靈活,通過組合實現很多不同的行為。 缺點,太靈活,增加了複雜性,會產生很多小類。

6. 總結

有一點模糊,不是很理解。需要繼續學習。

7. headfirst讀書分享

  1. 類應該對拓展開放,對修改關閉(開放-關閉原則)