2011-06-08 291 views
2

我試圖獲得更好的感覺,以便如何維護可能交換的類的訂閱(更改策略)。即使這些例子是人爲設計的,我也會盡量保持這一點。如何管理對可交換類的訂閱

假設有一類皮膚

public class Skin 
{ 
    //Raised when the form needs to turn on/off a blinking light 
    public event BlinkEventHandler BlinkEvent; 
    //The back color that forms should use 
    public Color BackColor{ get; protected set; } 
} 

應用程序啓動時,它會讀取的目錄全配置文件用於不同的皮膚類。用戶可以隨時切換當前皮膚。

我目前的工作使用了一個非常奇怪的策略(IMO),看起來像這樣:

/// <summary> 
/// Some class that can see when the Skin Changes 
/// </summary> 
public class SkinManager 
{ 
    //Raised when the Skin changes 
    public event SkinChangedEventHandler SkinChangedEvent; 
    private static Skin currentSkin; 
    public static Skin CurrentSkin {get;} 

    public SkinManager(){/* gets a skin into currentSkin */} 
    public void ChangeSkin() 
    { 
    //... do something to change the skin 
    if(SkinChangedEvent != null) 
    { 
     SkinChangedEvent(this, new SkinChangedEventArgs(/*args*/)); 
    } 
    } 
} 

/// <summary> 
/// Some form that follows the Skinning Strategy 
/// </summary> 
public class SkinnedForm : Form 
{ 
    private Skin skin; 
    public SkinnedForm() 
    { 
    skin = SkinManager.CurrentSkin; 
    if(skin != null) 
    { 
     skin.BlinkEvent += OnBlink; 
    } 
    SkinManager.SkinChangedEvent += OnSkinChanged; 
    } 

    private void OnSkinChanged(object sender, SkinChangedEventArgs e) 
    { 
    //unregister if we have a current skin 
    //the local was to ensure that the form unsubscribes 
    //when skin changes 
    if(skin != null) 
    { 
     skin.BlinkEvent -= OnBlink; 
    } 
    skin = SkinManager.CurrentSkin; 
    if(skin != null) 
    { 
     skin.BlinkEvent += OnBlink; 
    } 
    SkinChanged(); 
    } 

    private void SkinChanged(){ Invalidate(); } 

    private void OnBlink(object sender, BlinkEventArgs e) 
    { 
    //... do something for blinking 
    } 
} 

我無法相信這是一個很好的實施,反而會希望看到這樣的事情:

/// <summary> 
/// Some class that can see when the Skin Changes 
/// </summary> 
public class SkinManager 
{ 
    //Raised when the Skin changes 
    public event SkinChangedEventHandler SkinChangedEvent; 
    //Relays the event from Skin 
    public event BlinkEventHander BlinkEvent; 
    private static Skin currentSkin; 
    public static Skin CurrentSkin {get;} 

    public SkinManager() 
    { 
    //... gets a skin into currentSkin 
    currentSkin.BlinkEvent += OnBlink; 
    } 

    /// <summary> 
    /// Relays the event from Skin 
    /// </summary> 
    private void OnBlink(object sender, BlinkEventArgs e) 
    { 
    if(BlinkEvent != null) 
    { 
     BlinkEvent(this, e); 
    } 
    } 
    public void ChangeSkin() 
    { 
    //... do something to change the skin 
    if(SkinChangedEvent != null) 
    { 
     SkinChangedEvent(this, new SkinChangedEventArgs(/*args*/)); 
    } 
    } 
} 

/// <summary> 
/// Some form that follows the Skinning Strategy 
/// </summary> 
public class SkinnedForm : Form 
{ 
    //Do not need the local anymore 
    //private Skin skin; 
    public SkinnedForm() 
    { 
    SkinManager.CurrentSkin.BlinkEvent += OnBlink; 
    SkinManager.SkinChangedEvent += OnSkinChanged; 
    } 

    private void OnSkinChanged(object sender, SkinChangedEventArgs e) 
    { 
    //Only register with the manager, so no need to deal with 
    //subscription maintenance, could just directly to go SkinChanged(); 
    SkinChanged(); 
    } 

    private void SkinChanged() { Invalidate(); } 

    private void OnBlink(object sender, BlinkEventArgs e) 
    { 
    //... do something for blinking 
    } 
} 

我不確定這是否清楚,但主要是有一個局部變量,嚴格使用,以確保我們在訂閱新類上的事件之前取消訂閱事件。我認爲它是這樣的:我們實施了蒙皮策略模式(選擇您想要使用的蒙皮策略並運行它),但是每個策略實施都有我們直接訂閱的事件。當戰略發生變化時,我們希望我們的訂閱者觀看正確的發佈者,以便我們使用當地人。再次,我認爲這是一種可怕的方法。

是否有一個我爲使用管理器來監控所管理的類的所有事件並將它們傳遞的策略而改變並且訂戶繼續偵聽正確事件通知的轉換的名稱?所提供的代碼是在我形成問題時即時創建的,因此可以原諒任何錯誤。

回答

1

通常,當您想要爲引發事件的類創建代理(包裝器)時,您需要取消提交(分離)前一個實例,交換一個新實例,然後訂閱(附加)其事件。

比方說,你的皮膚界面看起來是這樣的:

interface ISkin 
{ 
    void RenderButton(IContext ctx); 
    event EventHandler Blink; 
} 

然後,你改變它需要看起來像這樣的部分:

public void SetSkin(ISkin newSkin) 
{ 
    // detach handlers from previous instance 
    DetachHandlers(); 

    // swap the instance 
    _skin = newSkin; 

    // attach handlers to the new instance 
    AttachHandlers(); 
} 

void DetachHandlers() 
{ 
    if (_skin != null) 
     _skin.Blink -= OnBlink; 
} 

void AttachHandlers() 
{ 
    if (_skin != null) 
     _skin.Blink += OnBlink; 
} 

完全代理會是這個樣子:

interface IChangeableSkin : ISkin 
{ 
    event EventHandler SkinChanged; 
} 

public class SkinProxy : IChangeableSkin 
{ 
    private ISkin _skin; // actual underlying skin 

    public void SetSkin(ISkin newSkin) 
    { 
     if (newSkin == null) 
      throw new ArgumentNullException("newSkin"); 

     if (newSkin == _skin) 
      return; // nothing changed 

     // detach handlers from previous instance 
     DetachHandlers(); 

     // swap the instance 
     _skin = newSkin; 

     // attach handlers to the new instance 
     AttachHandlers(); 

     // fire the skin changed event 
     SkinChanged(this, EventArgs.Empty); 
    } 

    void DetachHandlers() 
    { 
     if (_skin != null) 
      _skin.BlinkEvent -= OnBlink; 
    } 

    void AttachHandlers() 
    { 
     if (_skin != null) 
      _skin.BlinkEvent += OnBlink; 
    } 

    void OnBlink(object sender, EventArgs e) 
    { 
     // just forward the event 
     BlinkEvent(this, e); 
    } 

    // constructor 
    public SkinProxy(ISkin initialSkin) 
    { 
     SetSkin(initialSkin); 
    } 


    #region ISkin members 

    public void RenderButton(IContext ctx) 
    { 
     // just calls the underlying implementation 
     _skin.RenderButton(ctx); 
    } 

    // this is fired inside OnBlink 
    public event EventHandler BlinkEvent = delegate { }; 

    #endregion 


    #region IChangeableSkin members 

    public event EventHandler SkinChanged = delegate { }; 

    #region 
} 

您的表單應該只包含對實施的引用。

+0

太棒了,謝謝。這正是我所提議的,但要確保我沒有脫離基地。那麼這就是所謂的代理呢?出於某種原因,他們在這裏稱之爲提供者,但是當我查找模式時,它似乎不匹配。 – MPavlak 2011-06-08 14:51:49

1

種類繁雜,切換負擔由訂戶承擔。這不太好。

當交換外觀時,舊皮膚可能會刪除其事件訂戶,並可能將它們附加到新外觀。

但一個整潔的圖案可能是一個皮膚的持有人不會改變,並暴露事件。

+0

我同意這不是一個好的情況,你有什麼地方。 建議的變化與您所提議的皮膚持有者不同嗎?管理者也可以作爲持有人嗎? – MPavlak 2011-06-08 14:19:09

+0

是的,我認爲你的經理大致相同。 – 2011-06-08 14:23:49

0

SkinnedForm可以有型I皮膚的屬性 -

public class SkinnedForm : Form 
{ 
    private ISkin _Skin; 
    ... 
} 

通過公共財產暴露這一點,並且在任何時候設置。這樣,SkinnedForm永遠不會關心ISkin如何工作,或者它包含的事件模型。當您傳入新的Skin類別參考時,新的OnBlink事件將自動接管。實現ISkin的類應該包含OnBlink的邏輯。

然後,您將擁有一個管理員類(與您指定的距離不太遠)可以獲得對新外觀的引用,以及相關的SkinnedForm。經理的唯一工作是更新SkinnedForm上的ISkin屬性。

+0

哇,我不知道保持一個本地接口將管理我的訂閱。要驗證,你是說... SkinnedForm(){_ Skin = SkinManager.CurrentSkin; _Skin.BlinkEvent + = OnBlink; GetDifferentSkin();} GetDifferentSkin(){_ Skin = SomeOtherSkin;} 調用GetDifferentSkin之後,事件仍然連接? – MPavlak 2011-06-08 14:15:30

+0

嗨,不是。我在說,在任何一點上,SkinnedForm都應該有一個對單個Skin對象的引用,它只有一個BlinkEvent實現。如果換出整個Skin對象,則不需要註銷/重新註冊新事件。特別是不在SkinnedForm對象中,它不應該關心Skin事件是如何實現的,它應該只知道它有一個皮膚,並且可以在該皮膚上調用方法/引發事件。 – christofr 2011-06-08 14:25:57

+0

唷,我以爲我在那裏語言的一大部分缺失了一秒。我做了一些測試,發現確實沒有做自動交換。我相信你的建議與Groo提出的建議類似。謝謝:D – MPavlak 2011-06-08 14:48:59