Decorator Pattern(裝飾器模式)
是什麼?
Decorator Pattern 動態地為物件附加額外的職責。它用「包裝」的方式擴充功能,不需要修改原始類別,也不需要建立大量的子類別。每個 Decorator 包住前一個物件,形成一條裝飾鏈。
ℹ️GoF 分類
Decorator 屬於結構型模式(Structural Pattern),重點在於如何組合類別和物件以形成更大的結構。
什麼時候用?
- 你想在不修改原始類別的前提下增加功能
- 你需要靈活組合多種功能(而非每種組合都開一個子類別)
- 功能可以一層一層疊加上去(日誌、加密、壓縮、快取)
什麼時候不該用?
⚠️過度設計警告
如果功能組合很少(只有一兩種),直接用繼承或在原始類別中加方法更簡單。Decorator 一層包一層的結構在除錯時會比較難追蹤呼叫鏈。裝飾器的順序也可能影響結果(先加密再壓縮 vs 先壓縮再加密),需要特別注意。
執行流程
定義 Component 介面
宣告原始物件和裝飾器共用的方法
實作具體 Component
基礎物件,提供核心功能
建立 Decorator 基底類別
實作 Component 介面,持有另一個 Component 的參考
實作具體 Decorator
在委派給內部 Component 的前後加上新功能
組合裝飾
一層包一層,動態組合所需的功能
流程解讀:Decorator 的關鍵是「介面相同 + 遞迴包裝」。Decorator 和 ConcreteComponent 實作相同介面,所以 Decorator 可以包 ConcreteComponent,也可以包另一個 Decorator。每個 Decorator 在調用 inner.GetContent() 的結果上疊加自己的邏輯。組合時由外往內包裝(new SignedMessage(new TimestampedMessage(new PlainMessage(...)))),執行時由外往內委派。
程式碼範例
C# 版本
// Component 介面
public interface IMessage
{
string GetContent();
}
// 具體 Component
public class PlainMessage : IMessage
{
private readonly string _text;
public PlainMessage(string text) { _text = text; }
public string GetContent() => _text;
}
// Decorator 基底
public abstract class MessageDecorator : IMessage
{
protected readonly IMessage Inner;
protected MessageDecorator(IMessage inner) { Inner = inner; }
public abstract string GetContent();
}
// 具體 Decorator:加密
public class EncryptedMessage : MessageDecorator
{
public EncryptedMessage(IMessage inner) : base(inner) { }
public override string GetContent()
{
var original = Inner.GetContent();
return $"[ENCRYPTED]{Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(original))}[/ENCRYPTED]";
}
}
// 具體 Decorator:加上時間戳記
public class TimestampedMessage : MessageDecorator
{
public TimestampedMessage(IMessage inner) : base(inner) { }
public override string GetContent()
=> $"[{DateTime.Now:HH:mm:ss}] {Inner.GetContent()}";
}
// 具體 Decorator:加上簽名
public class SignedMessage : MessageDecorator
{
private readonly string _signer;
public SignedMessage(IMessage inner, string signer) : base(inner)
{
_signer = signer;
}
public override string GetContent()
=> $"{Inner.GetContent()} -- Signed by {_signer}";
}
// 使用:自由組合裝飾器
IMessage msg = new PlainMessage("Hello, Design Patterns!");
msg = new TimestampedMessage(msg);
msg = new SignedMessage(msg, "Alice");
Console.WriteLine(msg.GetContent());
// [14:30:00] Hello, Design Patterns! -- Signed by AliceTypeScript 版本
// Component 介面
interface Message {
getContent(): string;
}
// 具體 Component
class PlainMessage implements Message {
constructor(private text: string) {}
getContent(): string {
return this.text;
}
}
// 具體 Decorator:時間戳記
class TimestampedMessage implements Message {
constructor(private inner: Message) {}
getContent(): string {
const time = new Date().toLocaleTimeString();
return `[${time}] ${this.inner.getContent()}`;
}
}
// 具體 Decorator:簽名
class SignedMessage implements Message {
constructor(private inner: Message, private signer: string) {}
getContent(): string {
return `${this.inner.getContent()} -- Signed by ${this.signer}`;
}
}
// 具體 Decorator:大寫轉換
class UpperCaseMessage implements Message {
constructor(private inner: Message) {}
getContent(): string {
return this.inner.getContent().toUpperCase();
}
}
// 使用
let msg: Message = new PlainMessage("Hello, Design Patterns!");
msg = new TimestampedMessage(msg);
msg = new SignedMessage(msg, "Alice");
console.log(msg.getContent());Python 版本
from abc import ABC, abstractmethod
from datetime import datetime
# Component 介面
class Message(ABC):
@abstractmethod
def get_content(self) -> str:
pass
# 具體 Component
class PlainMessage(Message):
def __init__(self, text: str):
self._text = text
def get_content(self) -> str:
return self._text
# 具體 Decorator:時間戳記
class TimestampedMessage(Message):
def __init__(self, inner: Message):
self._inner = inner
def get_content(self) -> str:
time = datetime.now().strftime("%H:%M:%S")
return f"[{time}] {self._inner.get_content()}"
# 具體 Decorator:簽名
class SignedMessage(Message):
def __init__(self, inner: Message, signer: str):
self._inner = inner
self._signer = signer
def get_content(self) -> str:
return f"{self._inner.get_content()} -- Signed by {self._signer}"
# 具體 Decorator:大寫轉換
class UpperCaseMessage(Message):
def __init__(self, inner: Message):
self._inner = inner
def get_content(self) -> str:
return self._inner.get_content().upper()
# 使用
msg: Message = PlainMessage("Hello, Design Patterns!")
msg = TimestampedMessage(msg)
msg = SignedMessage(msg, "Alice")
print(msg.get_content())Java 版本
// Component 介面
public interface Message {
String getContent();
}
// 具體 Component
public class PlainMessage implements Message {
private final String text;
public PlainMessage(String text) { this.text = text; }
public String getContent() { return text; }
}
// 具體 Decorator:時間戳記
public class TimestampedMessage implements Message {
private final Message inner;
public TimestampedMessage(Message inner) { this.inner = inner; }
public String getContent() {
String time = java.time.LocalTime.now().toString().substring(0, 8);
return "[" + time + "] " + inner.getContent();
}
}
// 具體 Decorator:簽名
public class SignedMessage implements Message {
private final Message inner;
private final String signer;
public SignedMessage(Message inner, String signer) {
this.inner = inner;
this.signer = signer;
}
public String getContent() {
return inner.getContent() + " -- Signed by " + signer;
}
}
// 使用
Message msg = new PlainMessage("Hello, Design Patterns!");
msg = new TimestampedMessage(msg);
msg = new SignedMessage(msg, "Alice");
System.out.println(msg.getContent());結構圖
結構解讀:Decorator Base 同時實作 Component 介面(對外看起來和 ConcreteComponent 一樣)並持有另一個 Component 的參考(用來包裝)。每個 ConcreteDecorator(TimestampedMessage、SignedMessage)在委派給內部 Component 的前後加入自己的邏輯。關鍵是 Decorator 和 Component 介面相同,所以 Decorator 可以包裝另一個 Decorator,形成任意深度的裝飾鏈。
實戰補充
💡資深開發者經驗
Decorator 在 .NET 中最經典的應用是 Stream 的層層包裝:FileStream → BufferedStream → GZipStream → CryptoStream,每一層都是一個 Decorator。ASP.NET Core 的 Middleware Pipeline 也是 Decorator 的變體 — 每個 Middleware 包住下一個,依序處理 Request/Response。在 Java 世界,java.io 整個體系就是 Decorator Pattern 的教科書實作。設計時注意裝飾器的順序:先壓縮再加密和先加密再壓縮的結果完全不同。
理解測驗
🤔 Decorator Pattern 比繼承更靈活的原因是什麼?
🤔 以下哪個是 Decorator Pattern 在實務中的應用?
🤔 Decorator 和被裝飾的物件之間是什麼關係?
面試常見問題
Q: Decorator 和繼承各在什麼場景更合適?
A: Decorator 適合:功能組合多樣(如 A+B、A+C、B+C、A+B+C)、需要在執行時動態增減功能。繼承適合:功能組合固定且數量少、需要存取 protected 成員。判斷標準:如果 N 種可選功能用繼承需要 2^N 個子類別 → 用 Decorator。
Q: Decorator 的順序為什麼重要?
A: 因為每個 Decorator 在委派前後加入自己的邏輯,順序不同結果就不同。例如 Stream 處理:先壓縮再加密(資料先變小再加密,對方需要先解密再解壓)vs 先加密再壓縮(加密後的資料通常壓不動,效果差)。設計 Decorator 時必須文件化正確的堆疊順序。
相關模式
| 模式 | 關係 | |------|------| | Proxy | 結構相似但意圖不同 — Decorator 加功能,Proxy 控存取。Decorator 由外部組合,Proxy 通常自己管理被代理物件 | | Composite | 都用遞迴組合,但 Composite 是樹狀(一對多),Decorator 是線性鏈(一對一包裝) | | Strategy | 都能改變物件行為,但 Decorator 從外部「包裝」物件,Strategy 從內部「替換」行為 | | Chain of Responsibility | Decorator 鏈和 CoR 鏈結構相似,但 Decorator 每層都會執行,CoR 只有一個 Handler 處理 |
重點整理
💡一句話記住
Decorator Pattern = 手搖飲加料:基底不變,一層層往外加。 口訣:「包裝加功能,順序有學問」
| 概念 | 說明 | |------|------| | Component(共用介面) | 原始物件和裝飾器共同的介面 | | ConcreteComponent | 基礎物件,提供核心功能 | | Decorator | 實作 Component 介面,持有另一個 Component | | 核心好處 | 比繼承更靈活的功能擴充,遵循開放封閉原則 | | 代價 | 多層包裝增加除錯難度,裝飾器順序可能影響結果 |