1. 程式人生 > >【趣味設計模式系列】之【裝飾器模式】

【趣味設計模式系列】之【裝飾器模式】

## 1. 簡介 裝飾器模式(Decorator Pattern):動態地給一個物件新增職責,就增加功能來說,裝飾器比生成子類更靈活。 ## 2. 示例 水果店需要給網上客戶發貨,除了包裝之外,需要對特定水果包裝加額外裝飾,比如加防偽標誌、加固、加急等額外功能,但在外部看來還是打包元件。 ![](https://img2020.cnblogs.com/blog/1765702/202009/1765702-20200910151944648-1588275968.png) 類圖設計 ![](https://img2020.cnblogs.com/blog/1765702/202009/1765702-20200910161904827-818531910.png) 水果包裝介面類Bag,介面方法pack,完成水果打包。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:13 * @Desc: 包裝介面 */ public interface Bag { void pack(); } ``` 蘋果、橘子、香蕉各自實現包裝介面,用自己的特定的外包裝。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:15 * @Desc: 蘋果包裝類 */ public class AppleBag implements Bag { @Override public void pack() { System.out.println("蘋果使用紙箱包裝"); } } ``` ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:15 * @Desc: 橘子包裝類 */ public class OrangeBag implements Bag { @Override public void pack() { System.out.println("橘子使用網兜包裝"); } } ``` ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:15 * @Desc: 香蕉包裝類 */ public class BananaBag implements Bag { @Override public void pack() { System.out.println("香蕉使用竹籮包裝"); } } ``` 裝飾器類BagDecorator,實現包裝類Bag介面,同時擁有包裝介面的引用,為了組裝更多具體裝飾器加入進來,增加包裝類的裝飾功能。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:23 * @Desc: 裝飾器類 */ public class BagDecorator implements Bag { private Bag bag; //維持一個對抽象構件物件的引用 public BagDecorator(Bag bag) //注入一個抽象構件型別的物件 { this.bag = bag; } public void pack() { bag.pack(); } } ``` 防偽裝飾器CheckedBagDecorator,繼承BagDecorator類,並增加自己的防偽標識方法checked。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:32 * @Desc: 防偽裝飾器 */ public class CheckedBagDecorator extends BagDecorator { public CheckedBagDecorator(Bag bag) { super(bag); } @Override public void pack() { super.pack(); checked(); //列印防偽標識 } //增加防偽標識 public void checked() { System.out.println("==============="); System.out.println("列印上防偽標識"); } } ``` 加固裝飾器ReinforceBagDecorator,繼承BagDecorator類,並增加自己的加固方法reinforce。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:34 * @Desc: 加固裝飾器 */ public class ReinforceBagDecorator extends BagDecorator{ public ReinforceBagDecorator(Bag bag) { super(bag); } public void pack() { super.pack(); //呼叫原有業務方法 reinforce(); } //加固包裝 public void reinforce() { System.out.println("==============="); System.out.println("加固了包裝"); } } ``` 加急裝飾器SpeedBagDecorator,繼承BagDecorator類,並增加自己的加急方法speedy。 ``` package com.wzj.decorator; /** * @Author: wzj * @Date: 2020/9/8 10:34 * @Desc: 加急裝飾器 */ public class SpeedBagDecorator extends BagDecorator { public SpeedBagDecorator(Bag bag) { super(bag); } public void pack() { super.pack(); //呼叫原有業務方法 speedy(); } //快件加急 public void speedy() { System.out.println("==============="); System.out.println("打上加急標識"); } } ``` 客戶端類 ``` package com.wzj.decorator; import org.aspectj.weaver.ast.Or; /** * @Author: wzj * @Date: 2020/9/8 10:40 * @Desc: */ public class Client { public static void main(String[] args) { AppleBag appleBag = new AppleBag(); OrangeBag orangeBag = new OrangeBag(); BananaBag bananaBag = new BananaBag(); // 蘋果紙箱包裝後,外加防偽標識、加固包裝 new ReinforceBagDecorator(new CheckedBagDecorator(appleBag)).pack(); System.out.println("*********************************"); // 橘子網兜包裝後,外加防偽標識、加固包裝 new SpeedBagDecorator(new ReinforceBagDecorator(new CheckedBagDecorator(orangeBag))).pack(); } } ``` 結果 ``` 蘋果使用紙箱包裝 =============== 列印上防偽標識 =============== 加固了包裝 ********************************* 橘子使用網兜包裝 =============== 列印上防偽標識 =============== 加固了包裝 =============== 打上加急標識 ``` 從上述例子可以看出,裝飾器的好處,不僅可以對具體的水果包裝類進行裝飾,多個裝飾器還可以**`巢狀裝飾`**,非常靈活,這也是為什麼,裝飾器中需要引用Bag類,就是方便巢狀,因為每個具體的裝飾器,本身也是Bag的子類。 ## 3. 原始碼分析 Java IO類庫非常龐大,從大類分,如果從按流的方向來分的話,分為輸入流InputStream,輸出流OutputStream,如果按照讀取的方式分的話,分為位元組流與字元流,具體如下圖 ![](https://img2020.cnblogs.com/blog/1765702/202009/1765702-20200910185419552-1620078586.png) 針對不同的讀取和寫入場景,Java IO 又在這四個父類基礎之上,擴展出了很多子類,如下圖 ![](https://img2020.cnblogs.com/blog/1765702/202009/1765702-20200910211347298-1232777386.png) OutputStream 是一個抽象類,FileOutputStream 是專門用來寫檔案流的子類,FilterOutputStream很特殊,它實現了OutputStream,同時持有OutputStream的引用,部分原始碼如下圖: ``` public class FilterInputStream extends InputStream { /** * The input stream to be filtered. */ protected volatile InputStream in; ... ``` 所以FilterOutputStream在設計的時候本質就是一個裝飾器,其子類BufferedOutputStream,DataOutputStream,PrintStream都是具體的裝飾器,實現額外的功能。 同樣FilterInputStream、InputStreamReader、OutputStreamWriter都是裝飾器類,其子類充當具體裝飾的功能。 在平時寫程式碼的時候都有如下幾行巢狀的寫法: ``` File f = new File("c:/work/test.data"); FileOutputStream fos = new FileOutputStream(f); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw); bw.write("https://githup.com"); bw.flush(); bw.close(); ``` 本質上,OutputStream的實現類可以與具體裝飾器實現相互巢狀,具體的裝飾器之間也可以相互巢狀,非常靈活,避免了獨立為每個類建立子類而產生累爆炸的不合理設計。 ## 4. 總結 ### 4.1 優點 * 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。 * 處理可以撤銷的職責。 * 擴充套件子類靈活,避免產生累爆炸。 ### 4.2 缺點 * 如果最裡面的裝飾器出錯,需要從外面一層一層往裡面去檢視,多層巢狀導致增加復