著作權聲明:本文由http://leaver.me 翻譯,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者博客鏈接,謝謝!
背景
本文討論裝飾器模式,這個模式是因為很多情況下需要動態的給對象添加功能.比如我們創建了一個Stream類.后來需要對這個數據流類動態的添加一個加密功能.有人可能說把加密方法寫到流類里面啊.然后使用一個bool變量來控制開關就行了.但是這樣.這個加密方法只能寫一種..如果用派生類來實現.那么..對於不同的加密方法.,都要創建一個子類,舉個例子.比如有時候是一些函數的組合.我們最終的派生類的數目基本上就和排列組合的數目一樣了.
我們使用裝飾器模式來解決這個問題.GoF描述為
"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."
首先看一下圖.理解一下這個模式中每一個類的作用

• Component:定義了可以動態添加功能的具體類ConcreteComponents的接口.
• ConcreteComponent: 可以動態添加功能的具體類
• Decorator: 定義了動態添加到ConcreteComponent類中的功能的接口
• ConcreteDecorator: 可以添加到 ConcreteComponent.中的具體功能類.
使用代碼
我們開一個面包店的例子.面包店賣蛋糕和甜點.客戶可以買蛋糕和甜點,同時添加一些額外的東西.額外的東西包括奶油(Cream),櫻桃(Cherry),香料(Scent)和會員(Name Card)
如果我們用派生類來實現..那么我們會有如下的類
• CakeOnly
• CakeWithCreamAndCherry
• CakeWithCreamAndCherryAndScent
• CakeWithCreamAndCherryAndScentAndNameCard
• CakeWithCherryOnly
• PastryOnly
• PastryWithCreamAndCherry
• PastryWithCreamAndCherryAndScent
• PastryWithCreamAndCherryAndScentAndNameCard
• PastryWithCherryOnly
• 等等等等
這簡直就是噩夢..我們用裝飾器模式來實現把.
首先定義Component 接口
public abstract class BakeryComponent { public abstract string GetName(); public abstract double GetPrice(); }
前面說過了.這個類定義了能夠動態添加功能的具體類(ConcreteComponents)的接口,好吧.然后來創建具體類ConcreteComponents
class CakeBase : BakeryComponent { // 真實世界里,這個值應該來自數據庫等 private string m_Name = "Cake Base"; private double m_Price = 200.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } } class PastryBase : BakeryComponent { //真實世界里,這個值應該來自數據庫等 private string m_Name = "Pastry Base"; private double m_Price = 20.0; public override string GetName() { return m_Name; } public override double GetPrice() { return m_Price; } }
現在基對象准備好了.看看那些可以被動態添加的功能.我們看看Decorator 類
public abstract class Decorator : BakeryComponent { BakeryComponent m_BaseComponent = null; protected string m_Name = "Undefined Decorator"; protected double m_Price = 0.0; protected Decorator(BakeryComponent baseComponent) { m_BaseComponent = baseComponent; } #region BakeryComponent Members string BakeryComponent.GetName() { return string.Format("{0}, {1}", m_BaseComponent.GetName(), m_Name); } double BakeryComponent.GetPrice() { return m_Price + m_BaseComponent.GetPrice(); } #endregion }
注意兩個地方.第一個就是類實現BakeryComponent 接口,原因是裝飾后的蛋糕還是蛋糕,另一個是該類也持有一個BakeryComponent 對象,原因是,我們需要Cake和裝飾的項目是is-a關系,但是事實上不是.通過加一個對象就可以模擬is-a關系.
一句話.我們使用繼承實現了靜態的is-a關系,而是用構成則是一個動態的is-a關系.
然后看看ConcreteDecorators 如何實現
class ArtificialScentDecorator : Decorator { public ArtificialScentDecorator(BakeryComponent baseComponent) : base(baseComponent) { this.m_Name = "Artificial Scent"; this.m_Price = 3.0; } } class CherryDecorator : Decorator { public CherryDecorator(BakeryComponent baseComponent) : base(baseComponent) { this.m_Name = "Cherry"; this.m_Price = 2.0; } } class CreamDecorator : Decorator { public CreamDecorator(BakeryComponent baseComponent) : base(baseComponent) { this.m_Name = "Cream"; this.m_Price = 1.0; } }
然后看一下如何給一個會員卡添加一個打折的信息.
class NameCardDecorator : Decorator { private int m_DiscountRate = 5; public NameCardDecorator(BakeryComponent baseComponent) : base(baseComponent) { this.m_Name = "Name Card"; this.m_Price = 4.0; } public override string GetName() { return base.GetName() + string.Format("\n(Please Collect your discount card for {0}%)", m_DiscountRate); } }
現在我們的客戶端可使用Decorator 來裝飾ConcreteComponents 生成不同的組合.看看例子
static void Main(string[] args) { // 創建一個基本的蛋糕 CakeBase cBase = new CakeBase(); PrintProductDetails(cBase); // 添加奶油 CreamDecorator creamCake = new CreamDecorator(cBase); PrintProductDetails(creamCake); // 添加櫻桃 CherryDecorator cherryCake = new CherryDecorator(creamCake); PrintProductDetails(cherryCake); // 添加香料 ArtificialScentDecorator scentedCake = new ArtificialScentDecorator(cherryCake); PrintProductDetails(scentedCake); // 添加一張會員卡 NameCardDecorator nameCardOnCake = new NameCardDecorator(scentedCake); PrintProductDetails(nameCardOnCake); // 添加一個甜點 PastryBase pastry = new PastryBase(); PrintProductDetails(pastry); // 添加 奶油和櫻桃 CreamDecorator creamPastry = new CreamDecorator(pastry); CherryDecorator cherryPastry = new CherryDecorator(creamPastry); PrintProductDetails(cherryPastry); }
運行效果
亮點在那里
裝飾器模式是很典型的開放-封閉原則的例子.我們的類對擴展開放,而對修改封閉.
Demo下載
DecoratorSampleApp.zip
原文地址:UnderstandingplusandplusImplementingplusDecoratorp
著作權聲明:本文由http://leaver.me 翻譯,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者博客鏈接,謝謝!


