當需要為物件動態添加職責,且希望比繼承更靈活、避免類別爆炸時,裝飾模式是首選:將物件包裝在裝飾者中,裝飾者實作相同介面,持有被裝飾物件參考,轉發呼叫並在前後插入額外邏輯。多個裝飾者可疊加組合,形成靈活的行為管道。本文以訊息系統為例,展示如何用 CheckUserWrapper(用戶驗證)、CheckInputWrapper(輸入驗證)動態裝飾 SqlMessage,並支援雙重裝飾組合。
AbstractMessage,定義 Get()、Insert() 共同介面。SqlMessage、XmlMessage,核心業務實作。AbstractMessageWrapper,實作 AbstractMessage,持有 _abstractMessage 參考,預設轉發呼叫。CheckUserWrapper、CheckInputWrapper,覆寫方法在轉發前後添加邏輯。
public abstract class AbstractMessage
{
public abstract List<MessageModel> Get();
public abstract bool Insert(MessageModel mm);
}
public class SqlMessage : AbstractMessage
{
public override List<MessageModel> Get()
{
var l = new List<MessageModel>();
l.Add(new MessageModel("SQL方式獲取Message", DateTime.Now));
return l;
}
public override bool Insert(MessageModel mm) { return true; }
}
public abstract class AbstractMessageWrapper : AbstractMessage
{
private AbstractMessage _abstractMessage;
public AbstractMessageWrapper(AbstractMessage abstractMessage)
{
this._abstractMessage = abstractMessage;
}
public override List<MessageModel> Get() => _abstractMessage.Get();
public override bool Insert(MessageModel mm) => _abstractMessage.Insert(mm);
}
關鍵:預設實作完全透明轉發,具體裝飾者只需覆寫需增強的方法。
CheckUserWrapper(用戶驗證裝飾):
public class CheckUserWrapper : AbstractMessageWrapper
{
public CheckUserWrapper(AbstractMessage abstractMessage) : base(abstractMessage) { }
public override List<MessageModel> Get()
{
var l = base.Get(); // 先取得核心結果
foreach (var mm in l)
mm.Message += "(經過用戶驗證)"; // 後處理:標記
return l;
}
public override bool Insert(MessageModel mm)
{
// 前處理:驗證邏輯(此處略)
return base.Insert(mm);
}
}
CheckInputWrapper(輸入驗證裝飾):結構相同,標記「經過輸入驗證」。
AbstractMessage message = new SqlMessage();
// 單一裝飾
var amr = new CheckUserWrapper(message);
Response.Write(amr.Get()[0].Message); // SQL方式獲取Message(經過用戶驗證)
var amr2 = new CheckInputWrapper(message);
Response.Write(amr2.Get()[0].Message); // SQL方式獲取Message(經過輸入驗證)
// 雙重裝飾:順序很重要!
var amr3 = new CheckUserWrapper(message); // 內層:用戶驗證
var amr4 = new CheckInputWrapper(amr3); // 外層:輸入驗證
Response.Write(amr4.Get()[0].Message);
// SQL方式獲取Message(經過用戶驗證)(經過輸入驗證)
SQL方式獲取Message(經過用戶驗證) 2007-5-13 19:34:01
SQL方式獲取Message(經過輸入驗證) 2007-5-13 19:34:01
SQL方式獲取Message(經過用戶驗證)(經過輸入驗證) 2007-5-13 19:34:01
完美驗證:裝飾者疊加順序決定執行順序(外層先執行前處理、內層後執行後處理),行為靈活組合。
| 維度 | 繼承 | 策略模式 | 裝飾模式 |
|---|---|---|---|
| 靈活度 | 編譯期固定 | 執行期可換演算法 | 執行期可疊加多重行為 |
| 組合方式 | Is-A (單一) | Has-A (單一策略) | Has-A (多層包裝) |
| 擴展性 | 類別爆炸風險 | 新增策略類別 | 新增裝飾類別,任意組合 |
| 適用場景 | 核心身份固定 | 演算法整體替換 | 橫切關注點疊加 |
new Outer(new Inner(core)) 執行流程:
Outer.Before → Inner.Before → Core → Inner.After → Outer.After
如:Logging(Transaction(Authorization(Core))) → 日誌記錄交易邊界,交易包含授權檢查。
FileStream → BufferedStream → CryptoStream → GZipStream → StreamReader,經典裝飾模式應用。UseA → UseB → UseC 本質是裝飾 RequestDelegate 管道。HttpMessageHandler 裝飾鏈,支援重試、日誌、驗證、快取。DbCommandInterceptor 裝飾命令執行。Policy.Wrap(PolicyA, PolicyB) 組合重試、斷路器、超時策略。AbstractMessage 方法過多,裝飾者需全部轉發(即使不關心)。IMessageReader、IMessageWriter,裝飾者只實作關心介面,或使用「空物件模式」預設實作。Insert 成功才標記驗證通過)。decorated is SqlMessage 為 false,需用模式匹配或介面判斷。裝飾模式以「包裝轉發 + 前後增強」實現物件行為的動態疊加組合,是「組合優於繼承」原則的教科書級應用。本範例以用戶驗證、輸入驗證雙重裝飾 SQL 訊息查詢為例,完整展示了抽象構件、具體構件、抽象裝飾者、具體裝飾者四角色協作,驗證了裝飾順序決定執行順序的洋蔥模型語義。實務中,從 Stream 處理管道、HTTP Middleware、重試策略組合到 AOP 攔截器,裝飾模式無處不在。掌握其核心——「透明轉發基礎、單一職責增強、任意疊加組合、順序語義明確」,能讓你在設計「橫切關注點模組化、行為管道可配置、核心邏輯純淨」的系統時,構建出極具彈性與可維護性的架構。