公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml
今天來介紹裝飾者模式(Decorator Design Pattern)。java
假設咱們須要給一家火鍋店設計一套結帳系統,也就是統計顧客消費的總價格。怎樣才能設計出一個好的系統呢?git
既然要設計一個結帳系統,固然須要知道火鍋店都有哪些食品及食品的價格,假如咱們從火鍋店老闆那裏獲得如下食品清單:github
能夠看到,食品共有三大類,分別是:鍋底類,配菜類和飲料類,每一個大類下邊都有不少具體的食品。算法
爲了設計出一個可維護,可擴展,有彈性的系統,應該怎樣設計呢?shell
咱們能夠這樣看待食品之間的關係,將鍋底類看做主品,全部其它的都爲副品,也就是附加在主品之上的食品。設計模式
副品以主品爲中心,圍繞在主品周圍,包裹着主品,一層層的往外擴展。框架
以下圖所表達的同樣:ide
像這種,須要在原來(主品)的基礎上,附加其它的東西(副品),這樣的業務場景均可以使用裝飾者模式。測試
裝飾者模式的定義爲:動態的給一個對象添加其它功能。從擴展性來講,這種方式比繼承更有彈性,更加靈活,可做爲替代繼承的方案。
裝飾者模式的優勢在於,它可以更靈活的,動態的給對象添加其它功能,而不須要修改任何現有的底層代碼。也就是不須要經過修改代碼,而是經過擴展代碼,來完成新的業務需求。
這就很是符合咱們所說的設計原則中的開閉原則:對擴展開放,對修改關閉。也就是儘可能不要修改原有代碼,而是經過擴展代碼來完成任務。這樣作的好處是能夠減小對原有系統的修改,從而減小引入 bug 的風險。
裝飾者模式的類圖以下:
ConcreteComponent 爲被裝飾者,Decorator 是全部裝飾者的超類。
裝飾者和被裝飾者有着共同的超類型,這一點很重要,由於裝飾者必須可以取代被裝飾者。這樣,裝飾者才能在被裝飾者的基礎上,加上本身的行爲,以加強被裝飾者的能力。
一個被裝飾者能夠被多個裝飾者依次包裝,這個包裝行爲是動態的,不限次數的。
那麼根據裝飾者模式的類圖,咱們能夠設計出火鍋店結帳系統的類圖,以下:
火鍋的鍋底做爲被裝飾者,配菜和飲料做爲裝飾者。
每一個類都有兩個方法:
首先編寫 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
類中有兩個屬性 desc
和 price
,還有三個方法 description
,cost
和 printMenu
。printMenu
用於輸出菜單和消費總價。
再編寫 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
,而且重寫了兩個方法 description
和 cost
。
注意:SideDish
類對兩個方法 description
和 cost
進行了重寫,這很是重要,這體現出了裝飾者與被裝飾者的區別,裝飾者能在被裝飾者的基礎上附加本身的行爲,緣由就在這裏。
編寫兩個鍋底類:
class SoupPot extends HotPot { public SoupPot() { desc = "Soup"; price = 5; } } class SpicyPot extends HotPot { public SpicyPot() { desc = "Spicy"; price = 7; } }
這兩個類都繼承HotPot
,並分別在構造方法中設置本身的 desc
和 price
。
再編寫三個配菜類:
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
,並分別在構造方法中設置本身的hotpot
,desc
和 price
。
用以下代碼來測試:
// 只有一份清湯鍋底 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
方法,直到被裝飾者爲止。
而後再朝着最外層裝飾者的方向,依次計算出每一層的價格,最後得出的價格就是消費總價。
計算過程以下圖所示:
我將完整的裝飾者模式代碼放在了這裏,供你們參考。
裝飾者模式主要用於,在不修改原有類的前提下,動態的修改原有類的功能。
Java JDK 中大量使用了裝飾者模式,尤爲是 Java I/O 框架。
Java IO 框架的繼承關係以下:
能夠看到,Java IO 框架包含了很是多的類,這對初學者並非很友好,很難弄明白每一個類的做用是什麼,也不容易瞭解設計者的意圖。
Java IO 主要分爲字節流和字符流兩大類。咱們以 InputStream 爲例,畫出其類圖結構,以下:
從該圖可以看出,Java IO 與咱們上文中的裝飾者模式的類圖基本如出一轍,因此 Java IO 其實就是使用了裝飾者模式,明白了這一點,再使用它就很是方便了。
裝飾者模式有一個比較明顯的缺點,從上文中你也許已經發現了,就是它會引入很是多的小類,這樣會讓使用者弄不明白類之間的關係。
當了解了裝飾者的原理,也就比較容易使用了。
從裝飾者模式中,能充分的看到開閉原則的使用。利用裝飾者模式,可讓咱們在不修改原有代碼的狀況下,擴展原有類的功能。可是也不能過分使用它,由於容易引入很是多的小類。
(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。