- 裝飾模式
- 裝飾模式的特點(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類圖:
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ò)展》