設(shè)計(jì)模式(9) 裝飾模式

  • 裝飾模式
  • 裝飾模式的特點(diǎn)
  • 動(dòng)態(tài)撤銷功能

裝飾模式可以動(dòng)態(tài)向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。就增加功能來(lái)說(shuō),使用繼承的方式生成子類也可以達(dá)到目的,但隨著擴(kuò)展功能的不斷增加,子類的數(shù)量會(huì)快速膨脹,而裝飾模式提供了一種更加靈活的方案。

裝飾模式

GOF對(duì)裝飾模式的描述為:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
— Design Patterns : Elements of Reusable Object-Oriented Software

UML類圖:

裝飾模式 UML類圖

IComponent接口定義了現(xiàn)有的功能,ConcreteComponent是它的具體實(shí)現(xiàn)類。
為了給IComponent擴(kuò)展功能,引入了IDecorator接口,它繼承了IComponent接口,ConcreteDecorator是擴(kuò)展功能的具體實(shí)現(xiàn)。

為了更形象地理解這一模式,模擬實(shí)現(xiàn)一個(gè)文字處理軟件功能,最初軟件只具備單純的文字輸入、顯示功能,后來(lái)擴(kuò)展了更高級(jí)的功能,比如字體可以加粗、文字顏色可以調(diào)整、可以有不同的字號(hào)等等。

最初的功能

public interface IText
{
    string Content { get; }
}

public class TextObject : IText
{
    public string Content { get { return "hello"; } }
}

使用裝飾模式進(jìn)行擴(kuò)展

public interface IDecorator : IText { }

public abstract class DecoratorBase : IDecorator
{
    protected IText target;

    public abstract string Content { get; }

    public DecoratorBase(IText target)
    {
        this.target = target;
    }
}

//字體加粗
public class BoldDecorator : DecoratorBase
{
    public BoldDecorator(IText target) : base(target) { }

    public override string Content => ChangeToBoldFont(target.Content);

    public string ChangeToBoldFont(string content)
    {
        return $"<b>{content}</b>";
    }
}

//字體顏色
public class ColorDecorator : DecoratorBase
{
    public ColorDecorator(IText target) : base(target) { }

    public override string Content => AddColorTag(target.Content);

    public string AddColorTag(string content)
    {
        return $"<color>{content}</color>";
    }
}

測(cè)試代碼:

static void Main(string[] args)
{
    IText text = new TextObject();
    IDecorator text = new BoldDecorator(text);
    text = new ColorDecorator(text);
    Console.WriteLine(text.Content);
    //<color><b>hello</b></color>
}

裝飾模式是設(shè)計(jì)模式中實(shí)現(xiàn)技巧性非常明顯的一個(gè)模式,它的聲明要實(shí)現(xiàn)IComponent定義的方法,但同時(shí)又會(huì)保留一個(gè)IComponent的成員,IComponent接口方法的實(shí)現(xiàn)其實(shí)是通過(guò)自己保存的那個(gè)IComponent成員完成的,自己在這個(gè)基礎(chǔ)上增加一些額外的處理。

裝飾模式的特點(diǎn)

適用場(chǎng)景

  • 在不影響其他對(duì)象的情況下,以動(dòng)態(tài)、透明的方式給單個(gè)對(duì)象添加職責(zé)。畢竟客戶程序依賴的僅僅是IComponent接口,至于這個(gè)接口被做過(guò)什么裝飾只有實(shí)施裝飾的對(duì)象才知道,而客戶程序只負(fù)責(zé)根據(jù)IComponent的方法調(diào)用。
  • 屏蔽某些職責(zé),也就是在套用某個(gè)裝飾類型的時(shí)候,并不增加新的特征,而只把既有方法屏蔽。
  • 避免出現(xiàn)為了適應(yīng)變化而子類膨脹的情況。

缺點(diǎn)

裝飾模式雖然提供了比繼承更加靈活的擴(kuò)展方案,但也存在一些缺點(diǎn):

  • 開(kāi)發(fā)階段需要編寫(xiě)很多ConcreteDecorator類型。
  • 行態(tài)動(dòng)態(tài)組裝帶來(lái)的結(jié)果就是排查故障比較困難,從實(shí)際角度看,最后 IComponent的類型是最外層ConcreteDecorator的類型,但它的執(zhí)行過(guò)程是一系列ConcreteDecorator處理后的結(jié)果,追蹤和調(diào)試相對(duì)困難。

動(dòng)態(tài)撤銷功能

在實(shí)際場(chǎng)景中,除了動(dòng)態(tài)增加功能,往往還需要?jiǎng)討B(tài)撤銷某些功能,假設(shè)用裝飾模式來(lái)實(shí)現(xiàn)英雄聯(lián)盟中英雄購(gòu)買(mǎi)裝備的過(guò)程,買(mǎi)一件裝備,就相當(dāng)于動(dòng)態(tài)為英雄增加功能,但如果后期升級(jí)裝備需要賣(mài)掉一件現(xiàn)有的準(zhǔn)備時(shí),在實(shí)現(xiàn)上就涉及到這件裝備功能的卸載。
在比如前面代碼中的文字處理功能,字體加粗后可以撤銷,字體的顏色也支持更換,也需要功能的動(dòng)態(tài)撤銷,接上面的例子,實(shí)現(xiàn)撤銷的功能需要結(jié)合后面會(huì)學(xué)到的狀態(tài)模式,新增IState接口,引入了狀態(tài)的概念
支持撤銷功能的代碼如下:

//引入了狀態(tài)的概念
public interface IState
{
    bool Equals(IState newState);
}

//字體是否加粗可以用bool來(lái)表示
public class BoldState : IState
{
    public bool IsBold;
    public bool Equals(IState newState)
    {
        if (newState == null)
        {
            return false;
        }
        return ((BoldState)newState).IsBold == IsBold;
    }
}

//字體顏色的狀態(tài)比較多
public class ColorState : IState
{
    public Color Color = Color.Black;
    public bool Equals(IState newState)
    {
        if (newState == null)
        {
            return false;
        }
        return ((ColorState)newState).Color == Color;
    }
}

//基本功能
public interface IText
{
    string Content { get; }
}

public class TextObject : IText
{
    public string Content { get { return "hello"; } }

}

//裝飾接口,增加了狀態(tài)屬性和刷新?tīng)顟B(tài)的動(dòng)作
public interface IDecorator : IText
{
    IState State { get; set; }
    void Refresh<T>(IState newState) where T : IDecorator;
}

public abstract class DecoratorBase : IDecorator
{
    protected IText target;
    public DecoratorBase(IText target)
    {
        this.target = target;
    }
    public abstract string Content { get; }
    public IState State { get; set; }

    //更新?tīng)顟B(tài)
    public virtual void Refresh<T>(IState newState) where T : IDecorator
    {
        if (this.GetType() == typeof(T))
        {
            if (newState == null)
            {
                State = null;
            }
            if (State != null && !State.Equals(newState))
            {
                State = newState;
            }
        }
        if (target != null && typeof(IDecorator).IsAssignableFrom(target.GetType()))
        {
            ((IDecorator)target).Refresh<T>(newState);
        }
    }

}    

public class BoldDecorator : DecoratorBase
{
    public BoldDecorator(IText target) : base(target)
    {
        base.State = new BoldState();
    }

    public override string Content
    {
        get
        {
            if (((BoldState)State).IsBold)
            {
                return $"<b>{base.target.Content}</b>";
            }
            else
            {
                return base.target.Content;
            }
        }
    }
}

public class ColorDecorator : DecoratorBase
{
    public ColorDecorator(IText target) : base(target)
    {
        base.State = new ColorState();
    }

    public override string Content
    {
        get
        {
            if (State != null)
            {
                string colorName = (((ColorState)State).Color).Name;
                return $"<{colorName}>{base.target.Content}</{colorName}>";
            }
            else
            {
                return base.target.Content;
            }      
        }
    }
}

測(cè)試代碼

static void Main(string[] args)
{
    IText text = new TextObject();
    //默認(rèn)不加粗、黑色字體
    text = new BoldDecorator(text);
    text = new ColorDecorator(text);
    Console.WriteLine(text.Content);  //< Black > hello </ Black >

    //修改為加粗、紅色字體
    ColorState colorState = new ColorState();
    colorState.Color = Color.Red;
    BoldState boldState = new BoldState();
    boldState.IsBold = true;
    IDecorator root = (IDecorator)text;
    root.Refresh<ColorDecorator>(colorState);
    root.Refresh<BoldDecorator>(boldState);
    Console.WriteLine(text.Content); //< Red >< b > hello </ b ></ Red >

    //取消顏色設(shè)置
    colorState = null;
    root.Refresh<ColorDecorator>(colorState);
    Console.WriteLine(text.Content); //< b > hello </ b >
}

參考書(shū)籍:
王翔著 《設(shè)計(jì)模式——基于C#的工程化實(shí)現(xiàn)及擴(kuò)展》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容