設計模式(9) 裝飾模式

  • 裝飾模式
  • 裝飾模式的特點
  • 動態撤銷功能

裝飾模式可以動態向一個現有的對象添加新的功能,同時又不改變其結構。就增加功能來說,使用繼承的方式生成子類也可以達到目的,但隨着擴展功能的不斷增加,子類的數量會快速膨脹,而裝飾模式提供了一種更加靈活的方案。

裝飾模式

GOF對裝飾模式的描述爲:
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類圖:

IComponent接口定義了現有的功能,ConcreteComponent是它的具體實現類。
爲了給IComponent擴展功能,引入了IDecorator接口,它繼承了IComponent接口,ConcreteDecorator是擴展功能的具體實現。

爲了更形象地理解這一模式,模擬實現一個文字處理軟件功能,最初軟件只具備單純的文字輸入、顯示功能,後來擴展了更高級的功能,比如字體可以加粗、文字顏色可以調整、可以有不同的字號等等。

最初的功能

public interface IText
{
    string Content { get; }
}

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

使用裝飾模式進行擴展

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>";
    }
}

測試代碼:

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>
}

裝飾模式是設計模式中實現技巧性非常明顯的一個模式,它的聲明要實現IComponent定義的方法,但同時又會保留一個IComponent的成員,IComponent接口方法的實現其實是通過自己保存的那個IComponent成員完成的,自己在這個基礎上增加一些額外的處理。

裝飾模式的特點

適用場景

  • 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。畢竟客戶程序依賴的僅僅是IComponent接口,至於這個接口被做過什麼裝飾只有實施裝飾的對象才知道,而客戶程序只負責根據IComponent的方法調用。
  • 屏蔽某些職責,也就是在套用某個裝飾類型的時候,並不增加新的特徵,而只把既有方法屏蔽。
  • 避免出現爲了適應變化而子類膨脹的情況。

缺點

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

  • 開發階段需要編寫很多ConcreteDecorator類型。
  • 行態動態組裝帶來的結果就是排查故障比較困難,從實際角度看,最後 IComponent的類型是最外層ConcreteDecorator的類型,但它的執行過程是一系列ConcreteDecorator處理後的結果,追蹤和調試相對困難。

動態撤銷功能

在實際場景中,除了動態增加功能,往往還需要動態撤銷某些功能,假設用裝飾模式來實現英雄聯盟中英雄購買裝備的過程,買一件裝備,就相當於動態爲英雄增加功能,但如果後期升級裝備需要賣掉一件現有的準備時,在實現上就涉及到這件裝備功能的卸載。
在比如前面代碼中的文字處理功能,字體加粗後可以撤銷,字體的顏色也支持更換,也需要功能的動態撤銷,接上面的例子,實現撤銷的功能需要結合後面會學到的狀態模式,新增IState接口,引入了狀態的概念
支持撤銷功能的代碼如下:

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

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

//字體顏色的狀態比較多
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"; } }

}

//裝飾接口,增加了狀態屬性和刷新狀態的動作
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; }

    //更新狀態
    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;
            }      
        }
    }
}

測試代碼

static void Main(string[] args)
{
    IText text = new TextObject();
    //默認不加粗、黑色字體
    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 >

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

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章