裝飾模式 裝飾模式的特點 動態撤銷功能 裝飾模式可以動態向一個現有的對象添加新的功能,同時又不改變其結構。就增加功能來說,使用繼承的方式生成子類也可以達到目的,但隨著擴展功能的不斷增加,子類的數量會快速膨脹,而裝飾模式提供了一種更加靈活的方案。 裝飾模式 GOF對裝飾模式的描述為: Attach a ...
- 裝飾模式
- 裝飾模式的特點
- 動態撤銷功能
裝飾模式可以動態向一個現有的對象添加新的功能,同時又不改變其結構。就增加功能來說,使用繼承的方式生成子類也可以達到目的,但隨著擴展功能的不斷增加,子類的數量會快速膨脹,而裝飾模式提供了一種更加靈活的方案。
裝飾模式
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#的工程化實現及擴展》