穿了馬甲你就牛逼了:裝飾者模式解析

本文是設計模式系列的第三篇文章,今天主要學習裝飾者模式。

不知道大家沒有有這樣一種感覺,在看書學習時,感覺都看懂了,可是過一段時間就忘,因此我們從開頭就先問自己幾個問題,過一段時間就回過頭了複習下這幾個問題,從而鞏固學到的知識,在你的大腦中將這些知識點串起來。 希望能不斷反覆的思考,將點成線,最終形成知識塊,消化掉它。

帶着問題學習

  1. 什麼是裝飾者模式?
  2. 什麼場景下需要使用裝飾者模式?
  3. 如何實現裝飾者模式?
  4. 常用框架或源碼中有哪些案例可以體現?

裝飾者模式的概念

我們先來看看裝飾者模式的說明:

裝飾者模式 動態的將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

新的設計原則:類應該對擴展開放對修改關閉。

上面的定義雖然說明了裝飾者模式的 “角色”,但是沒有說明怎麼在我們的實現中實際 “應用”它。

接下來我們學習下它的類圖,結合着類圖在仔細分析它:

從上面的類圖,我們再來理解下裝飾者模式

  • 裝飾者和被裝飾對象有相同的超類型
  • 你可以用一個或多個裝飾者包裝一個對象(component的具體組件)
  • 既然裝飾者和被裝飾對象有相同的超類型,所以在任何需要原始對象(被包裝)的場合,可以用裝飾過的對象代替它
  • 裝飾者可以在所委託被裝飾者的行爲 之前或者之後,加上自己的行爲,已達到特定的目的,後面我們在如何使用中會明確的看到這點
  • 對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象

使用場景

現在有一個咖啡店,可以售賣幾種不同的咖啡,比如 摩卡、卡布奇諾、瑪奇朵、康巴納等。而且每一種具體的咖啡還可以添加不同的調料,比如奶泡、焦糖、豆漿、摩卡等,要求加不同調料最終價格不同,這個場景如果使用 OO 思想來設計,你會怎麼做呢?

毫無疑問這個場景非常適合裝飾者模式,首先被裝飾者就是我們的咖啡品種,而裝飾者就是我們的不同調料,這樣在計算價格時可以一層層去委託最終得到結果,話不多說來看如果用裝飾者模式來裝飾我們的咖啡。

看了上面我們舉例的星巴克咖啡的例子,有沒有和裝飾者模式的類圖框架對應呢? 大家可以仔細思考下。

鞏固擴展

利用上面你的裝飾者模式現在出一個場景,我們測試下到底能不能在不改動現有代碼的前提下實現,體驗下設計模式的魅力。

點一杯雙倍摩卡豆漿奶泡拿鐵咖啡?

是不是很方便,我們利用裝飾者模式進行組合擴展,體驗到魅力了吧。。

具體實現

下面一我們一起結合上面的例子,看下代碼實現:

// 飲料的基礎類,即component
public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }
    // cost必須在子類實現
    public abstract double cost();
}

調料抽象類,即裝飾者類:

// 調料抽象類即裝飾者類,這個類必須要能替換 Beverage,所以要繼承自 Beverage 類
public abstract class CondimentDecorator extends Beverage {

    public abstract String getDescription();
}

現在有了基類,下面是一個具體的飲料:

// 藍山
public class BlueMountainCoffee extends Beverage {
    public BlueMountainCoffee() {
        description = "BlueMountainCoffee";
    }
    @Override
    public double cost() {
        return 0;
    }
}

// 卡布奇諾
public class Cappuccino extends Beverage {
    public Cappuccino() {
        description = "Cappuccino";
    }
    @Override
    public double cost() {
        return 23;
    }
}

// 意式濃縮咖啡
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    @Override
    public double cost() {
        return 25;
    }
}
// 拿鐵
public class Latte extends Beverage {
    public Latte() {
        description = "Latte";
    }
    @Override
    public double cost() {
        return .89;
    }
}

現在已經有了具體組件和抽象組件,對比裝飾者模式類圖我們實現具體的裝飾者:

// 摩卡是一個裝飾者,所以擴展自 CondimentDecorator
public class Mocha extends CondimentDecorator {
    Beverage beverage;
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Mocha";
    }

    // 首先調用委託被裝飾者對象,以計算價錢,然後再加上Mocha價錢
    @Override
    public double cost() {
        return .20 + beverage.cost();
    }
}
// 豆漿
public class Soy extends CondimentDecorator {
    Beverage beverage;
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",Soy";
    }
    @Override
    public double cost() {
        return 2.0 + beverage.cost();
    }
}

接下來就是展示裝飾者模式魅力的時候:

// 測試類
public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 一杯Espresso,不加調料
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + "$" + beverage.cost());
  // 一杯加摩卡和豆漿的藍山咖啡
        Beverage beverage1 = new BlueMountainCoffee();
        beverage1 = new Mocha(beverage1);
        beverage1 = new Soy(beverage1);
        System.out.println(beverage1.getDescription() + "$" + beverage1.cost());
    }
}

目前我們創建對象還都是硬編碼 new 出來的,不太友好,隨着後續我們學習了工廠模式就好了,持續學習吧。

現實中的裝飾者

這塊列舉些平時用到的 jdk 中的裝飾者模式體現

Java I/O

列出的順序是從裝飾者 -> 被裝飾者

LineNumberInputStream -> BufferedInputStream -> FileInputStream

一目瞭然吧,這個和我們上面講的裝飾者模式類圖基本上是一致的,相信在你再次閱讀 Jvaa I/O 包中的類時,你一定會發出 “哇” 的一聲驚歎。


全文完!fighting

原創真心不易,希望你能幫我個小忙唄,如果本文內容你覺得有所收穫,請幫忙點個“在看”唄,或者轉發分享讓更多的小夥伴看到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章