系統架構技能之設計模式—裝飾模式
一、上篇回顧
通過上篇的簡單描述,我們知道了橋接模式主要是為了解決,一個對象的多個維度的變化因素的變化太快,難以控制的問題,我們通過將每個維度的變化因素進行抽象, 然后我們的對象只要依賴于抽象即可,具體的實現調用我們不關心,通過對象組合的方式,我們就能組合出我們想要的對象。無疑這是一種非常靈活的也是滿足設計模式的原則的,抽象和實現分離,使他們各自發生變化都不受對方的影響。而且我們也講述了,使用橋接模式的幾個典型的場景,現在我們的實際項目中就有這樣的問題,我也是在項目的使用過程中加深對橋接模式的理解的,橋接模式為系統在多個維度的變化的適應性方面提供了很好的參考,特別適合底層框架的開發過程中使用,可以適應不同變化因素的改變。上篇橋接模式放出后,我也得到了不少朋友的反饋,很感謝大家,這里特別的感謝一個名叫“蕭蕭”的朋友,他指出了我在文章中給出的例子有些不妥之處,我本篇將會對上篇中給出的例子,從給出符合橋接模式的實現,這里對大家say sorry !
public interface IORM
{
ISave Save
{
get;
set;
}
IDelete Delete
{
get;
set;
}
ICreate Create
{
get;
set;
}
ICache Cache
{
get;
set;
}
IQuery Query
{
get;
set;
}
void Test();
}
我們在這個接口的定義中并沒有體現橋接模式中的變化因素的抽象的概念,我們修改為如下形式:
public interface IORM
{
ISave Save
{
get;
set;
}
IDelete Delete
{
get;
set;
}
ICreate Create
{
get;
set;
}
void Test();
}
public interface ICache
{
object Cache(object o);
}
public interface IQuery
{
object Query(string condition);
}
具體的緩存服務與查詢服務的實現:
public class Cache : ICache
{
public object Cache(object o){
return o;
}
}
public class Query: IQuery
{
public object Query(string condition){
return GetList(condition);
}
}
具體的ORM實現代碼如下:
public class ORM : IORM
{
private IQuery query=new Query();
private ICache cache=new Cache();
public ORM(IQuery query,ICache cache)
{
this.query=query;
this.cache=cache;
}
public ISave Save
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public IDelete Delete
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public ICreate Create
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public void Test()
{
///測試緩存對象!
this.cache.Cache(new object());
}
}
對于之前按照我給的思路的方式,請大家糾正下思路,我這里對大家造成的誤解,表示抱歉,因為我舉例子的時候,表述的和我們本文的初衷有些偏差,希望大家見諒,請大家繼續提出寶貴意見和建議。
二、摘要
本篇將會開始講述結構型模式中的很有技巧性的模式之一-裝飾模式,我們理解的裝飾就是給類穿個衣服,或者裝扮一下,讓某個類具有一個新的特性或者新的職責。我們在應對對于通過類的繼承來實現類擴展功能時候的膨脹的速度過快的問題,我想這個也是我們上一篇講述的橋接模式中提到的一些問題,不過裝飾模式和橋接模式的側重點稍微不同。裝飾模式更側重的是某個類型的功能經常的動態的增加的情況。那么如果我們通過繼承的方式來實現,那么將會是噩夢。繼承復繼承,繼承何其多,然而通過裝飾模式,我們就能很優雅的為某個類型添加新的職責。我們也知道,由于C#不支持多繼承,我們只能為某個類型添加新增功能的時候,我們只能單繼承+多實現接口的形式來為類型添加新的功能。而裝飾模式可以很優雅的解決這樣的問題。
裝飾模式的主要目的就是:動態的為某個類型添加新的職責。
我們將自己開發一個簡單的播放器,我們通過第一次裝飾可以為播放器增加播放音樂的功能,具體包括播放所有的音頻格式文件,通過第二次裝飾,我們可以為這個播放器添加播放視頻格式文件的功能,這時候,如果我們想要一個萬能的播放器,那么我們將通過2次裝飾來完成這個播放器的類型職責的添加。
三、本文大綱
a、上篇回顧。
b、摘要。
c、本文大綱。
d、裝飾模式的特點及使用場景。
e、裝飾模式的經典實現。
f、裝飾模式的其他方案。
g、裝飾模式使用總結。
四、裝飾模式的特點及使用場景
4.1、裝飾模式的特點
裝飾模式:裝飾模式主要是解決了,我們以動態的,透明的形式為某個類型添加一個新的職責,客戶程序可以不知道我們具體添加的功能職責,而客戶程序只是根據對象提供的方法進行調用即可。而具體職責操作留給裝飾對象去完成。
我們這里給出裝飾模式的經典模式的類圖:
我們這里對這個經典結構進行稍微的說明:我們這里結合我們上面的播放器來說
1、對于Compnent,我們定義一個IPlayer這樣的接口,我們接口中定義方法,Play()方法。
2、然后我們把要裝飾這個播放器的裝飾類型進行抽象,抽象出接口IDecorator接口。這個即可繼承自IPlayer接口。
3、接著,我們定義一個簡單的播放器對象,實現這個IPlayer接口。
4、我們實現播放音樂和視頻文件的裝飾類型的實現。
5、我們將簡單的播放器對象,通過裝飾對象進行裝飾,為這個簡單播放器套上一層一層的外殼,使其具有播放音樂和視頻的功能。
4.2、裝飾模式的使用場景
1、當我們需要為某個現有的對象,動態的增加一個新的功能或職責時,可以考慮使用裝飾模式。
2、適應于某個對象的職責經常發生變化或者經常需要動態的增加職責,避免因為這種為了適應這樣的變化,而增加繼承子類擴展的方式,因為這種方式為 造成,子類膨脹的速度過快,難以控制。
五、裝飾模式的經典實現
經過上面的簡單描述,我想大家很想知道裝飾模式的經典實現方案吧!我們來結合播放器的實例來分析實現,下面給出核心代碼:
/// 簡單播放器的統一接口
/// </summary>
public interface IPlayer
{
void Play();
}
/// <summary>
/// 播放器的普通實現類
/// </summary>
public class Player : IPlayer
{
#region IPlayer 成員
public void Play()
{
throw new NotImplementedException();
}
#endregion
}
// <summary>
// 裝飾器接口
// </summary>
public interface IDecorator : IPlayer
/// <summary>
/// 裝飾器的抽象基類
/// </summary>
public abstract class DecoratorBase : IDecorator
{
protected IPlayer play = null;
public DecoratorBase(IPlayer player)
{
this.play = player;
}
#region IPlayer 成員
public abstract void Play();
#endregion
}
/// <summary>
/// 音樂播放器裝飾器
/// </summary>
public class MusicDecorator : DecoratorBase
{
public MusicDecorator(IPlayer play)
: base(play)
{
}
public override void Play()
{
throw new NotImplementedException();
}
}
/// <summary>
/// 視頻播放器裝飾類
/// </summary>
public class VedioDecorator : DecoratorBase
{
public VedioDecorator(IPlayer play)
: base(play)
{
}
public override void Play()
{
throw new NotImplementedException();
}
}
具體的測試調用代碼如下:
{
IPlayer play = new Player();
play = new MusicDecorator(play);
play = new VedioDecorator(play);
}
經過了2次的裝飾后,該播放器對象,可以播放任何格式的文件。通過上面的過程,我們發現了我們在使用裝飾模式的時候,有如下幾點我們需要把我好:
1、我們在開發的過程中,如果需要為某個類型添加多個職責的時候,我們需要多個裝飾實現類,這樣類太多的時候,我們需要將這個對象裝飾多次。
2、我們對我們的裝飾過的對象,如何指定的撤銷某個裝飾過程的狀態呢?類似我們使用某些應用程序中的undo操作。
3、對于動態裝飾的對象,我們debug的時候,我們很難跟蹤和調試。
六、裝飾模式的其他方案
一、通過配置文件來完成裝飾操作
具體的操作過程如下:配置文件中配置對象要完成的裝飾步驟,通過公共的配置讀取類,通過字典緩存裝飾的步驟,然后傳入普通的對象,返回裝飾后的對象。
我們這里給出配置文件的格式:
<Decorator>
<Section>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
<Class name="" type="">
<Step name="" type=""/>
<Step name="" type=""/>
<Step name="" type=""/>
</Class>
</Section>
</Decorator>
我們來看具體的裝飾器步驟構造器代碼:
{
public static T BuilderPlay<T>(T play)
{
Dictionary<Type, List<IDecorator>> steps = new Dictionary<Type, List<IDecorator>>();
List<IDecorator> list = steps[typeof(T)];
foreach (IDecorator item in list)
{
play = (T)Activator.CreateInstance(item.GetType(), play);
}
return play;
}
}
二、通過特性+反射的形式來組織裝飾的過程 這里就不給出具體的實現代碼了,下篇或者后續給出分析幾類基于特性+標記的實現方案。大伙也可以提出其他的不同實現思路。
七、裝飾模式使用總結
通過上面的講述,我們知道裝飾模式,特別適合對某個類型的對象,動態的增加新的職責,應用程序就像使用原來的對象一樣使用對象新增的裝飾后的功能,裝飾模式就好像是穿了一層層的外殼,這樣的方式避免了通過繼承來為類型添加新的職責的形式可取,通過繼承的方式容易造成子類的膨脹,但是當裝飾類太多的時候,也是個難以維護的問題,至少是在裝飾對象的時候,我們可能需要多步操作來完成對象的裝飾,這時候我們可以同上面提出的改進的方案,來完成自動配置裝飾模式,記錄操作模式的狀態,可以進行有效的回滾操作,以完成撤銷操作。