裝飾模式 是結構型設計模式中最優雅的模式之一,核心意圖:**動態地給物件新增職責,且不改變其介面**。它是繼承的有力替代方案——繼承是靜態、編譯期決定;裝飾是動態、執行期組合。就像孫悟空七十二變,變成魚兒會游泳、變成鳥兒會飛行,但本質永遠是孫悟空 (介面不變),客戶端 (二郎神) 看穿變化仍能正確互動。
| 角色 | 說明 | 關鍵點 |
|---|---|---|
| Component (抽象構件) | 定義物件介面,規範核心行為 | 介面或抽象類別 |
| ConcreteComponent (具體構件) | 被裝飾的核心物件 | 實作 Component |
| Decorator (裝飾者) | 持有 Component 參考,實作 Component 介面 | 委派給被裝飾物件,可攔截/擴充 |
| ConcreteDecorator (具體裝飾者) | 新增具體職責 | Operation() 前後加入邏輯 |
using System;
// 抽象構件
public abstract class Component
{
public abstract void Operation();
}
// 具體構件
public class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("ConcreteComponent.Operation() - 核心業務邏輯");
}
}
// 抽象裝飾者:持有 Component 參考
public abstract class Decorator : Component
{
protected Component _component;
public Decorator(Component component)
{
_component = component ?? throw new ArgumentNullException(nameof(component));
}
// 預設行為:直接委派
public override void Operation()
{
_component?.Operation();
}
}
// 具體裝飾者 A:新增狀態
public class ConcreteDecoratorA : Decorator
{
private string _addedState = "預設狀態";
public ConcreteDecoratorA(Component component) : base(component) { }
public override void Operation()
{
base.Operation(); // 呼叫被裝飾物件
_addedState = "新狀態"; // 新增行為
Console.WriteLine("ConcreteDecoratorA.Operation() - 新增狀態管理");
}
}
// 具體裝飾者 B:新增行為
public class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(Component component) : base(component) { }
public override void Operation()
{
base.Operation();
AddedBehavior(); // 新增獨立行為
Console.WriteLine("ConcreteDecoratorB.Operation() - 新增獨立行為");
}
private void AddedBehavior()
{
Console.WriteLine(" -> 執行額外行為: 記錄日誌、效能計時、權限檢查...");
}
}
// 客戶端:像剝洋蔥一樣層層包裝
var component = new ConcreteComponent();
var d1 = new ConcreteDecoratorA(component);
var d2 = new ConcreteDecoratorB(d1);
// 也可鏈式建構
// var d2 = new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteComponent()));
d2.Operation();
/* 輸出順序 (由內而外):
ConcreteComponent.Operation() - 核心業務邏輯
ConcreteDecoratorA.Operation() - 新增狀態管理
-> 執行額外行為: 記錄日誌、效能計時、權限檢查...
ConcreteDecoratorB.Operation() - 新增獨立行為
*/
嚴格透明裝飾要求客戶端只依賴 `Component` 介面。現實中常需為裝飾者新增專屬方法 (如 `Fly()`),此為半透明:
// 抽象構件
public interface IMonkey
{
void Fight();
}
// 具體構件
public class SunWukong : IMonkey
{
public void Fight() => Console.WriteLine("孫悟空施展七十二變大戰妖怪!");
}
// 抽象裝飾者
public abstract class MonkeyDecorator : IMonkey
{
protected readonly IMonkey _monkey;
protected MonkeyDecorator(IMonkey monkey) => _monkey = monkey;
public virtual void Fight() => _monkey.Fight();
}
// 具體裝飾者:變成魚兒
public class FishForm : MonkeyDecorator
{
public FishForm(IMonkey monkey) : base(monkey) { }
public void Swim() => Console.WriteLine("變成魚兒在水中游泳~"); // 新增方法
public override void Fight()
{
Console.WriteLine("魚兒靈活閃避攻擊!");
base.Fight();
}
}
// 具體裝飾者:變成鳥兒
public class BirdForm : MonkeyDecorator
{
public BirdForm(IMonkey monkey) : base(monkey) { }
public void Fly() => Console.WriteLine("變成鳥兒在天空飛翔~"); // 新增方法
public override void Fight()
{
Console.WriteLine("鳥兒從空中俯衝攻擊!");
base.Fight();
}
}
// 客戶端:需轉型才能呼叫新方法 (半透明)
IMonkey monkey = new SunWukong();
monkey.Fight();
var fish = new FishForm(monkey);
fish.Fight();
fish.Swim(); // 需知道是 FishForm
var bird = new BirdForm(fish); // 疊加裝飾
bird.Fight();
bird.Fly();
// bird.Swim(); // 編譯錯誤:BirdForm 沒有 Swim()
// 抽象構件
public abstract class LibraryItem
{
public int Copies { get; set; }
public abstract void Display();
}
// 具體構件:書籍
public class Book : LibraryItem
{
public string Author { get; }
public string Title { get; }
public Book(string author, string title, int copies)
{
Author = author;
Title = title;
Copies = copies;
}
public override void Display()
{
Console.WriteLine($"📖 書籍: {Title} | 作者: {Author} | 館藏: {Copies} 冊");
}
}
// 具體構件:影片
public class Video : LibraryItem
{
public string Director { get; }
public string Title { get; }
public int DurationMinutes { get; }
public Video(string director, string title, int copies, int duration)
{
Director = director;
Title = title;
Copies = copies;
DurationMinutes = duration;
}
public override void Display()
{
Console.WriteLine($"🎬 影片: {Title} | 導演: {Director} | 館藏: {Copies} 份 | 時長: {DurationMinutes} 分鐘");
}
}
// 抽象裝飾者
public abstract class LibraryItemDecorator : LibraryItem
{
protected readonly LibraryItem _item;
protected LibraryItemDecorator(LibraryItem item) => _item = item;
public override void Display() => _item.Display();
}
// 具體裝飾者:可借閱功能
public class BorrowableDecorator : LibraryItemDecorator
{
private readonly List _borrowers = new();
public BorrowableDecorator(LibraryItem item) : base(item) { }
public void BorrowItem(string borrowerName)
{
if (_item.Copies <= 0)
{
Console.WriteLine($"❌ {_item.GetType().Name} 無庫存可借閱");
return;
}
_borrowers.Add(borrowerName);
_item.Copies--;
Console.WriteLine($"✅ {borrowerName} 借閱了 {_item.GetType().Name} (剩餘: {_item.Copies})");
}
public void ReturnItem(string borrowerName)
{
if (_borrowers.Remove(borrowerName))
{
_item.Copies++;
Console.WriteLine($"🔄 {borrowerName} 歸還了 {_item.GetType().Name} (剩餘: {_item.Copies})");
}
}
public override void Display()
{
base.Display();
if (_borrowers.Count > 0)
{
Console.WriteLine($" 📋 借閱人: {string.Join(", ", _borrowers)}");
}
}
}
// 具體裝飾者:預約功能 (可再裝飾在 Borrowable 之上)
public class ReservableDecorator : LibraryItemDecorator
{
private readonly Queue _reservations = new();
public ReservableDecorator(LibraryItem item) : base(item) { }
public void Reserve(string userName)
{
_reservations.Enqueue(userName);
Console.WriteLine($"📝 {userName} 預約了 {_item.GetType().Name} (隊列位置: {_reservations.Count})");
}
public string NotifyNext()
{
if (_reservations.TryDequeue(out string nextUser))
{
Console.WriteLine($"🔔 通知 {nextUser} 可借閱 {_item.GetType().Name}");
return nextUser;
}
return null;
}
public override void Display()
{
base.Display();
if (_reservations.Count > 0)
{
Console.WriteLine($" ⏳ 預約隊列: {string.Join(" → ", _reservations)}");
}
}
}
// 使用
var book = new Book("馮唐", "北京北京", 3);
var video = new Video("史匹柏", "辛德勒的名單", 2, 195);
// 只有影片加上可借閱
var borrowableVideo = new BorrowableDecorator(video);
borrowableVideo.BorrowItem("小王");
borrowableVideo.BorrowItem("小李");
borrowableVideo.Display();
// 書籍加上可借閱 + 可預約 (雙重裝飾)
var reservableBook = new ReservableDecorator(new BorrowableDecorator(book));
reservableBook.BorrowItem("小張");
reservableBook.Reserve("小陳");
reservableBook.Reserve("小劉");
reservableBook.Display();
// .NET Stream 類別階層即裝飾模式
// Stream (Component)
// ├── FileStream (ConcreteComponent)
// ├── MemoryStream (ConcreteComponent)
// ├── NetworkStream (ConcreteComponent)
// ├── BufferedStream (Decorator) - 新增緩衝
// ├── CryptoStream (Decorator) - 新增加密/解密
// ├── GZipStream / DeflateStream (Decorator) - 新增壓縮
// ├── StreamReader/Writer (Decorator) - 新增文字讀寫
// └── AuthenticatedStream (Decorator) - 新增認證
// 實際使用:層層包裝
using (var fileStream = new FileStream("data.bin", FileMode.Create))
using (var cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
using (var gzipStream = new GZipStream(cryptoStream, CompressionMode.Compress))
using (var writer = new StreamWriter(gzipStream))
{
writer.WriteLine("機密資料:這段文字將被壓縮 -> 加密 -> 寫入檔案");
}
// 讀取時反向包裝
using (var fileStream = new FileStream("data.bin", FileMode.Open))
using (var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress))
using (var cryptoStream = new CryptoStream(gzipStream, aes.CreateDecryptor(), CryptoStreamMode.Read))
using (var reader = new StreamReader(cryptoStream))
{
string text = reader.ReadToEnd();
Console.WriteLine($"解密後: {text}");
}
// Middleware 管線本質是裝飾模式
// RequestDelegate (Component) - 核心委派型別
// 中介軟體 (Decorator) - 包裝下一個 RequestDelegate
public class TimingMiddleware
{
private readonly RequestDelegate _next;
public TimingMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
// 前置:記錄請求資訊
Console.WriteLine($"[Timing] 開始: {context.Request.Method} {context.Request.Path}");
await _next(context); // 呼叫下一個中介軟體 (被裝飾物件)
// 後置:記錄回應與耗時
sw.Stop();
Console.WriteLine($"[Timing] 結束: {context.Response.StatusCode} 耗時 {sw.ElapsedMilliseconds}ms");
}
}
// 擴充方法註冊 (慣例)
public static class TimingMiddlewareExtensions
{
public static IApplicationBuilder UseTiming(this IApplicationBuilder builder)
{
return builder.UseMiddleware();
}
}
// Startup.cs 組裝管線 (裝飾順序 = 執行順序)
app.UseTiming(); // 最外層:計時
app.UseAuthentication(); // 驗證
app.UseAuthorization(); // 授權
app.UseMiddleware(); // 錯誤處理 (內層)
app.MapControllers(); // 終端:控制器執行
| 優點 | 缺點 |
|---|---|
| 比繼承更靈活:執行期動態組合功能 | 產生大量小物件:除錯困難、物件圖複雜 |
| 避免類別爆炸:排列組合替代子類別激增 | 身分識別混淆:裝飾後物件 != 原物件 (參考比較失效) |
| 單一職責:每個裝飾者只管一項功能 | 順序敏感:裝飾順序影響行為 (如加密後壓縮 vs 壓縮後加密) |
| 開放封閉:新增功能只需新增裝飾者 | 半透明破壞多型:客戶端需轉型呼叫專屬方法 |
裝飾模式以「包裝委派」實現動態功能擴充,是 **開放封閉原則** 的經典實踐。從 .NET `Stream` 體系、`TextReader/Writer`、ASP.NET Core Middleware、到企業級應用的權限、日誌、快取、重試等橫切關注點,裝飾模式無處不在。掌握其精髓——**介面不變、職責疊加、動態組合**——能讓你設計出靈活、可維護、可測試的系統架構。記住:像孫悟空七十二變,外形百變,本質不變,這就是裝飾模式的最高境界。