[原譯]理解並實現裝飾器模式


著作權聲明:本文由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 翻譯,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者博客鏈接,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM