聲明: 本文
內容屬於《Head First 設計模式》閱讀筆記
,文中涉及到的知識案例等直接或間接來源於該書。《Head First 設計模式》
通過有趣的圖表+文字的形式,讓人自然學習設計模式,非常棒
,推薦閱讀
。
裝飾者模式概念:
動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
要點說明:
序號 | 說明 |
---|---|
① | 裝飾者與被裝飾者需要具有同一個超類(即:這兩者的某個祖先類要相同)。 |
② | 程序需要的也只是超類,而不具體到哪一個子類,這樣一來該超類的任何子類都能被程序認可。 |
③ | 裝飾者中應持有被裝飾者實例,這樣一來,裝飾者裏面就能調用到被裝飾者的相關方法,進而進行裝飾。 |
④ | 裝飾者中持有被裝飾者時,聲明被裝飾者的類型時應聲明爲超類的類型(而不聲明爲某個具體的被裝飾者類)。這樣一來,(因爲裝飾者、被裝飾者都是這個超類的子類,所以)裝飾者不僅可以裝飾被裝飾者,裝飾者還可以裝飾裝飾者。 |
⑤ | 裝飾者應重寫(所有裝飾後)會受到影響的方法。 注:重寫的方法來自超類。 |
圖示(示例)說明:
案例(輔助理解):
提示: 裝飾者模式使用靈活,下面只是一個簡單的例子,在不同的情境下,可能有不同的表現形式。
情景(需求)介紹:
現有一家星巴茲咖啡館,裏面有各種咖啡(焦炒咖啡、低濃咖啡、濃縮咖啡、首選咖啡)以及各種調味料(牛奶、摩卡、豆漿、奶泡)。現在的需求是:客人在點咖啡時還可以點任意(任意種類、任意數量的)調料。
從裝飾者模式出發:
- 各種咖啡,各種調料的共同超類,爲飲料beverage。
- 各種咖啡爲飲料的直接實現,爲被裝飾者。
- 各種調料是飲料的附加添加品,爲裝飾者。
- 可以將裝飾者的共有邏輯進行抽取,抽取爲一個抽象類。
於是得到以下UML類圖:提示:上圖中沒有顯示方法項,裝飾者應重寫裝飾後受影響的方法(重寫的方法來自超類)。
上述裝飾者模式示例中的幾個核心類及代表類:
-
AbstractBeverage:抽象超類。
import lombok.Getter; import lombok.Setter; /** * 飲料(的抽象定義) * * 注: 在java中的裝飾者模式,超類既可採用【抽象類】,亦可採用【接口】。 * 不過一般都採用 抽象類。 * * @author JustryDeng * @date 2019/12/3 11:45 */ public abstract class AbstractBeverage { /** 描述 */ @Setter @Getter private String description; /** * 獲取飲料的價格 * * @return 飲料的價格 */ public abstract double cost(); }
注:超類既可採用抽象類,亦可採用接口。不過一般都採用抽象類。
-
AbstractCondimentDecorator:裝飾者抽象類。
import com.szlaozicl.designpattern.decorator.AbstractBeverage; /** * (裝飾飲料的)調料裝飾者(的抽象定義) * * @author JustryDeng * @date 2019/12/3 11:50 */ @SuppressWarnings("all") public abstract class AbstractCondimentDecorator extends AbstractBeverage { /** 被裝飾者 */ protected AbstractBeverage decoratedObj; /** * 構造器 * * @param decoratedObj * 被裝飾者 * @date 2019/12/4 12:30 */ protected AbstractCondimentDecorator(AbstractBeverage decoratedObj) { this.decoratedObj = decoratedObj; } /** * 裝飾者應重寫 飲料的描述 * 注: 之所以需要重寫飲料的描述, 是因爲原來的描述沒有對調料的介紹。 * 特別注意: 裝飾者 應重寫 裝飾前後相關的方法。 如,這裏的描述、價格。 * * @return 飲料的描述 */ @Override public abstract String getDescription(); }
注:對於所有裝飾者共有的特徵,可進行抽取,抽取爲抽象類或非抽象類均可。
-
Espresso:被裝飾者代表類。
import com.szlaozicl.designpattern.decorator.AbstractBeverage; /** * 具體飲料 - 濃縮咖啡 * * @author JustryDeng * @date 2019/12/3 12:03 */ public class Espresso extends AbstractBeverage { /** 無參構造 */ public Espresso() { // 當前飲料的描述 setDescription("濃縮咖啡Espresso"); } @Override public double cost() { return 1.99; } }
-
MilkCondimentDecorator:裝飾者代表類。
import com.szlaozicl.designpattern.decorator.AbstractBeverage; /** * 具體的調料(裝飾者) - 牛奶Milk * * @author JustryDeng * @date 2019/12/3 12:33 */ public class MilkCondimentDecorator extends AbstractCondimentDecorator { /** 構造 */ public MilkCondimentDecorator(AbstractBeverage decoratedObj) { super(decoratedObj); } @Override public String getDescription() { // 被裝飾者 原來的description String oldDescription = decoratedObj.getDescription(); // 對原來的description進行裝飾 return oldDescription + " + 牛奶Milk"; } @Override public double cost() { // 被裝飾者 原來的 價格 double oldCost = decoratedObj.cost(); // 對原來的價格進行裝飾 return oldCost + 0.1; } }
測試一下:
-
測試類:
import com.szlaozicl.designpattern.decorator.beverage.DarkRoast; import com.szlaozicl.designpattern.decorator.beverage.Decat; import com.szlaozicl.designpattern.decorator.beverage.Espresso; import com.szlaozicl.designpattern.decorator.decorator.MilkCondimentDecorator; import com.szlaozicl.designpattern.decorator.decorator.MochaCondimentDecorator; import com.szlaozicl.designpattern.decorator.decorator.SoyCondimentDecorator; import com.szlaozicl.designpattern.decorator.decorator.WhipCondimentDecorator; /** * 裝飾者模式 --- 測試 * * @author JustryDeng * @date 2019/12/3 12:54 */ public class Test { /** 函數入口 */ public static void main(String[] args) { System.out.println(" ------------- 測試一"); testOne(); System.out.println("\n ------------- 測試二"); testTwo(); System.out.println("\n ------------- 測試三"); testThree(); } /** 測試一 */ private static void testOne() { AbstractBeverage beverage = new Espresso(); System.err.println("裝飾前:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); // 給Espresso裝飾 牛奶Milk beverage = new MilkCondimentDecorator(beverage); System.err.println("裝飾後:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); } /** 測試二 */ private static void testTwo() { AbstractBeverage beverage = new DarkRoast(); System.err.println("裝飾前:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); // 給DarkRoast裝飾 牛奶Milk beverage = new MilkCondimentDecorator(beverage); /* * 繼續裝飾 豆漿Soy * * 注:雖然直接裝飾的對象是MilkCondimentDecorator,但是由 * 於MilkCondimentDecorator其實是對DarkRoast的裝飾, * 所以實際上裝飾的還是DarkRoast */ beverage = new SoyCondimentDecorator(beverage); System.err.println("裝飾後:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); } /** 測試三 */ private static void testThree() { AbstractBeverage beverage = new Decat(); System.err.println("裝飾前:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); // 給DarkRoast裝飾 奶泡Whip beverage = new WhipCondimentDecorator(beverage); // 繼續裝飾 摩卡Mocha beverage = new MochaCondimentDecorator(beverage); // 繼續裝飾 奶泡Whip beverage = new WhipCondimentDecorator(beverage); System.err.println("裝飾後:"); System.out.println("描述:" + beverage.getDescription()); System.out.println("價格:" + beverage.cost()); } }
運行測試類main方法,控制檯輸出:
拓展 - 自定義InputStream子類的裝飾者:
java.io包下大量運用了裝飾者模式(或在裝飾者模式基礎上進行了變形),如InputStream、OutputStream、Reader、Writer等。
-
以InputStream爲例,其UML類圖部分如圖:
-
自定義一個大寫轉換爲小寫的裝飾者,並編寫測試類:
import java.io.ByteArrayInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * 對java.io包下的I/O進行自定義裝飾者 測試 * <p> * 說明: java.io包下的I/O就大量用到了 裝飾者模式, * 這裏自己實現一個 InputStream的裝飾者 以作練習。 * * @author JustryDeng * @date 2019/12/3 12:54 */ @SuppressWarnings("all") public class IOTest { /** 函數入口 */ public static void main(String[] args) throws IOException { InputStream in = new LowerCaseInputStream(new ByteArrayInputStream("ABCDEFG".getBytes())); int c; while ((c = in.read()) > 0) { // 輸出結果爲: abcdefg System.out.print((char) c); } } } /** * 自定義InputStream子類的裝飾者 * * @author JustryDeng * @date 2019/12/3 13:17 */ @SuppressWarnings("all") class LowerCaseInputStream extends FilterInputStream { public LowerCaseInputStream(InputStream in) { super(in); } /** * 從此輸入流中讀取下一個數據字節。返回一個 0 到 255 範圍內的 int 字節值。 * 如果因爲已經到達流末尾而沒有字節可用,則返回 -1。 */ @Override public int read() throws IOException { int c = super.read(); if (c == -1) { return c; } return Character.toLowerCase((char) c); } /** * 從此輸入流中將 len 個字節的數據讀入到這個 給定的byte數組中。 */ @Override public int read(byte[] b, int offset, int len) throws IOException { int result = super.read(b, offset, len); // 注: len爲允許一次讀取的最大字節數, 而result爲該次實際讀取的字節數, 所以得以result爲準 // 注: 數據結果存儲在b中,只需要將b裏面的大寫字母轉換爲小寫即可 for (int i = offset; i < offset + result; i++) { b[i] = (byte) Character.toLowerCase((char) b[i]); } return result; } }
-
控制檯輸出:
提示:裝飾者模式可結合工廠模式/建造者模式進行優化。
裝飾者模式學習完畢 !
^_^ 如有不當之處,歡迎指正
^_^ 參考資料
《Head First 設計模式》Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著,O’Reilly Taiwan公司譯,UMLChina改編
^_^ 測試代碼託管鏈接
https://github.com/JustryDeng…DesignPattern
^_^ 本文已經被收錄進《程序員成長筆記(六)》,筆者JustryDeng