裝飾器模式屬於結構型模式,動態地給物件添加新職責,比繼承更靈活。本文通過「咖啡 + 牛奶/巧克力/糖」的經典案例,展示裝飾器結構、代碼實現、多層裝飾組合與遞歸調用鏈。
模式結構
┌──────────────┐
│ Component │◀────── 介面:定義核心行為 method()
└──────┬───────┘
│
├──────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ ConcreteComp│ │ Decorator │ (持有 Component 引用)
│ Coffee │ └──────┬──────┘
└─────────────┘ │
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ ConcreteDecoA │ │ ConcreteDecoB │
│ Milk+Sugar │ │ Chocolate │
└───────────────┘ └───────────────┘
核心代碼實現
// 1. 組件介面
interface Component {
void method();
}
// 2. 具體組件:基礎咖啡
class Coffee implements Component {
@Override public void method() {
System.out.println("倒入咖啡");
}
}
// 3. 抽象裝飾器:持有被裝飾對象,轉發核心方法
class Decorator implements Component {
protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public void method() { component.method(); }
}
// 4. 具體裝飾器 A:牛奶 + 糖
class ConcreteDecorateA extends Decorator {
public ConcreteDecorateA(Component c) { super(c); }
private void addMilk() { System.out.println("倒入牛奶"); }
private void addSugar() { System.out.println("加入糖"); }
@Override public void method() {
super.method(); // 先執行被裝飾對象邏輯
addMilk(); // 再添加自己的行為
addSugar();
}
}
// 5. 具體裝飾器 B:巧克力
class ConcreteDecorateB extends Decorator {
public ConcreteDecorateB(Component c) { super(c); }
private void addChocolate() { System.out.println("加入巧克力"); }
@Override public void method() {
super.method();
addChocolate();
}
}
客戶端組合使用
public class TestDecoratePattern {
public static void main(String[] args) {
// 基礎咖啡
Component coffee = new Coffee();
coffee.method();
// 輸出:倒入咖啡
System.out.println("--- 單層裝飾:牛奶+糖 ---");
Component withMilkSugar = new ConcreteDecorateA(coffee);
withMilkSugar.method();
// 輸出:倒入咖啡 → 倒入牛奶 → 加入糖
System.out.println("--- 雙層裝飾:(咖啡+牛奶+糖)+巧克力 ---");
Component withChoco = new ConcreteDecorateB(withMilkSugar);
withChoco.method();
// 輸出:倒入咖啡 → 倒入牛奶 → 加入糖 → 加入巧克力
System.out.println("--- 鏈式構建:巧克力(牛奶+糖(咖啡)) ---");
Component chain1 = new ConcreteDecorateB(new ConcreteDecorateA(new Coffee()));
chain1.method();
System.out.println("--- 逆序:牛奶+糖(巧克力(咖啡)) ---");
Component chain2 = new ConcreteDecorateA(new ConcreteDecorateB(new Coffee()));
chain2.method();
// 輸出順序不同:倒入咖啡 → 加入巧克力 → 倒入牛奶 → 加入糖
}
}
執行結果對比
| 構建方式 | 輸出順序 | 說明 |
| new ConcreteDecorateA(coffee) | 咖啡 → 牛奶 → 糖 | 單層裝飾 |
| new ConcreteDecorateB(withMilkSugar) | 咖啡 → 牛奶 → 糖 → 巧克力 | 外層裝飾在內層之後執行 |
| new ConcreteDecorateB(new ConcreteDecorateA(new Coffee())) | 咖啡 → 牛奶 → 糖 → 巧克力 | 鏈式構建,內層優先 |
| new ConcreteDecorateA(new ConcreteDecorateB(new Coffee())) | 咖啡 → 巧克力 → 牛奶 → 糖 | 裝飾順序改變行為順序 |
核心原理:遞歸轉發鏈
// 簡化的調用鏈
chain2.method() // ConcreteDecorateA.method()
→ super.method() // Decorator.method() → ConcreteDecorateB.method()
→ super.method() // Decorator.method() → Coffee.method()
→ "倒入咖啡"
→ "加入巧克力"
→ "倒入牛奶"
→ "加入糖"
每個裝飾器調用 `super.method()` 觸發內層邏輯,形成**洋蔥模型**調用鏈:請求從外層進入,逐層向內傳遞到核心組件,響應再逐層向外返回,各層可在前置/後置位置插入邏輯。
Java JDK 內建裝飾器實例
| 基礎類 | 裝飾器 | 增強功能 |
| InputStream | BufferedInputStream | 緩衝讀取 |
| InputStream | GZIPInputStream | 解壓縮 |
| InputStream | DataInputStream | 基本類型讀取 |
| OutputStream | BufferedOutputStream | 緩衝寫入 |
| Reader/Writer | BufferedReader/Writer | 緩衝字符讀寫 |
| Collection | Collections.synchronizedList() | 線程安全包裝 |
| Collection | Collections.unmodifiableList() | 不可變視圖 |
// 經典嵌套用法
new BufferedReader(
new InputStreamReader(
new GZIPInputStream(
new FileInputStream("data.gz"))));
裝飾器 vs 繼承 vs 組合
| 維度 | 繼承 | 裝飾器 | 組合(策略模式) |
| 靈活性 | 靜態、編譯期固定 | 動態、運行期組合 | 動態、運行期替換 |
| 擴展性 | 子類爆炸 | 單一職責、任意組合 | 策略類獨立擴展 |
| 透明性 | 子類即父類 | 裝飾器與被裝飾者同介面 | 持有策略介面 |
| 適用場景 | is-a 關係穩定 | 橫切關注點、動態增強 | 算法族、行為可插拔 |
最佳實踐提醒
- 保持介面精簡:Component 介面只定義核心行為,避免膨脹。
- 裝飾器不應依賴具體組件:只依賴 Component 介面,保證可嵌套任意層級。
- 避免過度設計:若擴展點固定、層級少,繼承或組合可能更簡單。
- 命名規範:`XxxDecorator` 或 `XxxWrapper`,語義清晰。
- Spring AOP 本質即動態代理裝飾器:理解裝飾器有助於掌握 AOP 原理。
- 上一篇文章: Java Socket 字節流傳輸實戰:從阻塞 I/O 到十六進制調試完整示例
- 下一篇文章: Struts 2 常用 Constant 配置全覽:從開發模式到國際化、主題、靜態資源一網打盡
Copyright ©
程式碼之家 All Rights Reserved