裝飾者模式-動態的包裝原有對象的行爲

公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml

今天來介紹裝飾者模式Decorator Design Pattern)。java

假設咱們須要給一家火鍋店設計一套結帳系統,也就是統計顧客消費的總價格。怎樣才能設計出一個好的系統呢?git

1,結帳系統需求分析

既然要設計一個結帳系統,固然須要知道火鍋店都有哪些食品及食品的價格,假如咱們從火鍋店老闆那裏獲得如下食品清單:github

  • 鍋底類:
    • 清湯鍋底:5 元
    • 麻辣鍋底:7 元
    • 其它
  • 配菜類:
    • 青菜:3 元
    • 羊肉:6 元
    • 其它
  • 飲料類:
    • 可樂:2 元
    • 其它

能夠看到,食品共有三大類,分別是:鍋底類,配菜類和飲料類,每一個大類下邊都有不少具體的食品。算法

爲了設計出一個可維護,可擴展,有彈性的系統,應該怎樣設計呢?shell

咱們能夠這樣看待食品之間的關係,將鍋底類看做主品,全部其它的都爲副品,也就是附加在主品之上的食品。設計模式

副品以主品爲中心,圍繞在主品周圍,包裹着主品,一層層的往外擴展框架

以下圖所表達的同樣:ide

在這裏插入圖片描述

2,裝飾者模式

像這種,須要在原來(主品)的基礎上,附加其它的東西(副品),這樣的業務場景均可以使用裝飾者模式測試

裝飾者模式的定義爲:動態的給一個對象添加其它功能。從擴展性來講,這種方式比繼承更有彈性,更加靈活,可做爲替代繼承的方案

裝飾者模式的優勢在於,它可以更靈活的,動態的給對象添加其它功能,而不須要修改任何現有的底層代碼。也就是不須要經過修改代碼,而是經過擴展代碼,來完成新的業務需求。

這就很是符合咱們所說的設計原則中的開閉原則對擴展開放,對修改關閉。也就是儘可能不要修改原有代碼,而是經過擴展代碼來完成任務。這樣作的好處是能夠減小對原有系統的修改,從而減小引入 bug 的風險。

裝飾者模式的類圖以下:

在這裏插入圖片描述

ConcreteComponent 爲被裝飾者,Decorator 是全部裝飾者的超類。

裝飾者和被裝飾者有着共同的超類型,這一點很重要,由於裝飾者必須可以取代被裝飾者。這樣,裝飾者才能在被裝飾者的基礎上,加上本身的行爲,以加強被裝飾者的能力。

一個被裝飾者能夠被多個裝飾者依次包裝,這個包裝行爲是動態的,不限次數的。

3,實現結帳系統

那麼根據裝飾者模式的類圖,咱們能夠設計出火鍋店結帳系統的類圖,以下:

在這裏插入圖片描述

火鍋的鍋底做爲被裝飾者,配菜和飲料做爲裝飾者。

每一個類都有兩個方法:

  • describe:返回當前火鍋的描述。
  • cost:返回當前火鍋的價格。

首先編寫 HotPot 類:

class HotPot {
    protected String desc = "HotPot";
    protected double price = 0;

    public String description() {
        return desc;
    }

    public double cost() {
        return price;
    }
 
    public void printMenu() {
        System.out.println("菜單:" + description() + " 消費總價:" + cost());
    }
}

HotPot 類中有兩個屬性 descprice,還有三個方法 descriptioncostprintMenuprintMenu 用於輸出菜單和消費總價。

再編寫 SideDish 類:

class SideDish extends HotPot {
    protected HotPot hotpot;
    
    public double cost() {
        return hotpot.cost() + price;
    };

    public String description() {
        return hotpot.description() +" + "+ desc;
    };
}

SideDish 繼承了 HotPot,添加了本身的屬性 hotpot,而且重寫了兩個方法 descriptioncost

注意SideDish 類對兩個方法 descriptioncost 進行了重寫,這很是重要,這體現出了裝飾者與被裝飾者的區別,裝飾者能在被裝飾者的基礎上附加本身的行爲,緣由就在這裏

編寫兩個鍋底類:

class SoupPot extends HotPot {
    public SoupPot() {
        desc = "Soup";
        price = 5;
    }
}

class SpicyPot extends HotPot {
    public SpicyPot() {
        desc = "Spicy";
        price = 7;
    }
}

這兩個類都繼承HotPot,並分別在構造方法中設置本身的 descprice

再編寫三個配菜類:

class VegetablesDish extends SideDish {
    public VegetablesDish(HotPot hotpot) {
        this.hotpot = hotpot;
        desc = "Vegetables";
        price = 3;
    }
}

class MuttonDish extends SideDish {
    public MuttonDish(HotPot hotpot) {
        this.hotpot = hotpot;
        desc = "Mutton";
        price = 6;
    }
}

class ColaDish extends SideDish {
    public ColaDish(HotPot hotpot) {
        this.hotpot = hotpot;
        desc = "Cola";
        price = 2;
    }
}

這三個類都繼承 SideDish,並分別在構造方法中設置本身的hotpotdescprice

4,測試結帳系統

用以下代碼來測試:

// 只有一份清湯鍋底
HotPot hotpot = new SoupPot(); // 被裝飾者不需裝飾者包裝也可使用
hotpot.printMenu();

// 清湯鍋底 + 蔬菜
hotpot = new VegetablesDish(hotpot);
hotpot.printMenu();

// 清湯鍋底 + 蔬菜 + 羊肉
hotpot = new MuttonDish(hotpot);
hotpot.printMenu();

// 清湯鍋底 + 蔬菜 + 羊肉 + 可樂
hotpot = new ColaDish(hotpot);
hotpot.printMenu();

// 清湯鍋底 + 蔬菜 + 羊肉 + 可樂 + 蔬菜
hotpot = new VegetablesDish(hotpot);
hotpot.printMenu();

輸出以下:

菜單:Soup 消費總價:5.0
菜單:Soup + Vegetables 消費總價:8.0
菜單:Soup + Vegetables + Mutton 消費總價:14.0
菜單:Soup + Vegetables + Mutton + Cola 消費總價:16.0
菜單:Soup + Vegetables + Mutton + Cola + Vegetables 消費總價:19.0

計算總價時,會從最外層的裝飾者,朝着被裝飾者的方向,依次調用每一層的 cost 方法,直到被裝飾者爲止。

而後再朝着最外層裝飾者的方向,依次計算出每一層的價格,最後得出的價格就是消費總價。

計算過程以下圖所示:

在這裏插入圖片描述

我將完整的裝飾者模式代碼放在了這裏,供你們參考。

5,裝飾者模式的使用場景

裝飾者模式主要用於,在不修改原有類的前提下,動態的修改原有類的功能。

Java JDK 中大量使用了裝飾者模式,尤爲是 Java I/O 框架。

Java IO 框架的繼承關係以下:

在這裏插入圖片描述

能夠看到,Java IO 框架包含了很是多的類,這對初學者並非很友好,很難弄明白每一個類的做用是什麼,也不容易瞭解設計者的意圖。

Java IO 主要分爲字節流字符流兩大類。咱們以 InputStream 爲例,畫出其類圖結構,以下:

在這裏插入圖片描述

從該圖可以看出,Java IO 與咱們上文中的裝飾者模式的類圖基本如出一轍,因此 Java IO 其實就是使用了裝飾者模式,明白了這一點,再使用它就很是方便了。

6,裝飾者模式的缺點

裝飾者模式有一個比較明顯的缺點,從上文中你也許已經發現了,就是它會引入很是多的小類,這樣會讓使用者弄不明白類之間的關係。

當了解了裝飾者的原理,也就比較容易使用了。

7,總結

從裝飾者模式中,能充分的看到開閉原則的使用。利用裝飾者模式,可讓咱們在不修改原有代碼的狀況下,擴展原有類的功能。可是也不能過分使用它,由於容易引入很是多的小類。

(本節完。)


推薦閱讀:

設計模式之高質量代碼

單例模式-讓一個類只有一個對象

工廠模式-將對象的建立封裝起來

策略模式-定義一個算法族

觀察者模式-將消息通知給觀察者


歡迎關注做者公衆號,獲取更多技術乾貨。

碼農充電站pro

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息