2011-06-08 1092 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