首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 設計模式深度解析:裝飾模式動態擴充物件功能的藝術

     編輯:關於C#

前言

裝飾模式 是結構型設計模式中最優雅的模式之一,核心意圖:**動態地給物件新增職責,且不改變其介面**。它是繼承的有力替代方案——繼承是靜態、編譯期決定;裝飾是動態、執行期組合。就像孫悟空七十二變,變成魚兒會游泳、變成鳥兒會飛行,但本質永遠是孫悟空 (介面不變),客戶端 (二郎神) 看穿變化仍能正確互動。

模式結構與角色

角色說明關鍵點
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()

實戰案例 1:圖書館借閱系統 (原文改良)


// 抽象構件
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();

實戰案例 2:.NET Stream 裝飾器模式經典應用


// .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}");
}

實戰案例 3:ASP.NET Core Middleware 管線


// 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();      // 終端:控制器執行

簡化裝飾模式的實作指南

  1. 單一 ConcreteComponent:若只有一個具體構件,可省略抽象 Component,Decorator 直接繼承 ConcreteComponent
  2. 單一 ConcreteDecorator:若只有一種裝飾,可合併 Decorator 與 ConcreteDecorator 為單一類別
  3. 建構子注入 vs Setter 注入:建構子注入確保不可變性 (推薦);Setter 注入允許動態切換被裝飾物件
  4. 輕量 Component:Component 介面盡量精簡,避免將過多邏輯放入基類

優缺點分析

優點缺點
比繼承更靈活:執行期動態組合功能產生大量小物件:除錯困難、物件圖複雜
避免類別爆炸:排列組合替代子類別激增身分識別混淆:裝飾後物件 != 原物件 (參考比較失效)
單一職責:每個裝飾者只管一項功能順序敏感:裝飾順序影響行為 (如加密後壓縮 vs 壓縮後加密)
開放封閉:新增功能只需新增裝飾者半透明破壞多型:客戶端需轉型呼叫專屬方法

最佳實務

  • ✅ 介面隔離:Component 介面精簡,只定義核心契約
  • ✅ 不可變裝飾:建構子注入 Component,建立後不變更
  • ✅ 文件化順序:明確標註裝飾順序對行為的影響
  • ✅ 避免過度裝飾:超過 3-4 層考慮重構為策略模式或管線模式
  • ✅ 單元測試:獨立測試每個裝飾者邏輯,Mock 被裝飾物件

總結

裝飾模式以「包裝委派」實現動態功能擴充,是 **開放封閉原則** 的經典實踐。從 .NET `Stream` 體系、`TextReader/Writer`、ASP.NET Core Middleware、到企業級應用的權限、日誌、快取、重試等橫切關注點,裝飾模式無處不在。掌握其精髓——**介面不變、職責疊加、動態組合**——能讓你設計出靈活、可維護、可測試的系統架構。記住:像孫悟空七十二變,外形百變,本質不變,這就是裝飾模式的最高境界。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved