1. 簡介
裝飾器模式(Decorator Pattern):動態地給一個物件新增職責,就增加功能來說,裝飾器比生成子類更靈活。
2. 示例
水果店需要給網上客戶發貨,除了包裝之外,需要對特定水果包裝加額外裝飾,比如加防偽標誌、加固、加急等額外功能,但在外部看來還是打包元件。
類圖設計
水果包裝介面類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,如果按照讀取的方式分的話,分為位元組流與字元流,具體如下圖
針對不同的讀取和寫入場景,Java IO 又在這四個父類基礎之上,擴充套件出了很多子類,如下圖
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 缺點
- 如果最裡面的裝飾器出錯,需要從外面一層一層往裡面去檢視,多層巢狀導致增加複雜性。