Decorator Pattern(裝飾器模式)

是什麼?

Decorator Pattern 動態地為物件附加額外的職責。它用「包裝」的方式擴充功能,不需要修改原始類別,也不需要建立大量的子類別。每個 Decorator 包住前一個物件,形成一條裝飾鏈。

ℹ️GoF 分類

Decorator 屬於結構型模式(Structural Pattern),重點在於如何組合類別和物件以形成更大的結構。

什麼時候用?

什麼時候不該用?

⚠️過度設計警告

如果功能組合很少(只有一兩種),直接用繼承或在原始類別中加方法更簡單。Decorator 一層包一層的結構在除錯時會比較難追蹤呼叫鏈。裝飾器的順序也可能影響結果(先加密再壓縮 vs 先壓縮再加密),需要特別注意。

執行流程

1

定義 Component 介面

宣告原始物件和裝飾器共用的方法

2

實作具體 Component

基礎物件,提供核心功能

3

建立 Decorator 基底類別

實作 Component 介面,持有另一個 Component 的參考

4

實作具體 Decorator

在委派給內部 Component 的前後加上新功能

5

組合裝飾

一層包一層,動態組合所需的功能

流程解讀:Decorator 的關鍵是「介面相同 + 遞迴包裝」。Decorator 和 ConcreteComponent 實作相同介面,所以 Decorator 可以包 ConcreteComponent,也可以包另一個 Decorator。每個 Decorator 在調用 inner.GetContent() 的結果上疊加自己的邏輯。組合時由外往內包裝(new SignedMessage(new TimestampedMessage(new PlainMessage(...)))),執行時由外往內委派。

程式碼範例

C# 版本

csharp
// 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 Alice

TypeScript 版本

typescript
// 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 版本

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 版本

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());

結構圖

Client
uses
Component (Message)
ConcreteComponent (PlainMessage)
implements
Decorator Base
implements & wraps
TimestampedMessage
extends
SignedMessage

結構解讀:Decorator Base 同時實作 Component 介面(對外看起來和 ConcreteComponent 一樣)並持有另一個 Component 的參考(用來包裝)。每個 ConcreteDecorator(TimestampedMessage、SignedMessage)在委派給內部 Component 的前後加入自己的邏輯。關鍵是 Decorator 和 Component 介面相同,所以 Decorator 可以包裝另一個 Decorator,形成任意深度的裝飾鏈。

實戰補充

💡資深開發者經驗

Decorator 在 .NET 中最經典的應用是 Stream 的層層包裝FileStreamBufferedStreamGZipStreamCryptoStream,每一層都是一個 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 | | 核心好處 | 比繼承更靈活的功能擴充,遵循開放封閉原則 | | 代價 | 多層包裝增加除錯難度,裝飾器順序可能影響結果 |

你可能也想看

Composite PatternFacade Pattern

按 ← → 鍵切換課程