如何管理可交换类的订阅

时间:2011-06-08 13:44:04

标签: c# events design-patterns

我正在尝试更好地了解如何维持订阅可能交换的类(Change Strategies)。即使这些例子都是人为的,我也会尽力保持这种指导。

假设有一个类皮肤

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; }
}

当应用程序启动时,它将读取一个充满不同Skin类的配置文件的目录。用户可以随时切换当前皮肤。

我目前的工作采用了一种非常奇怪的策略(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
  }
}

我不确定这是否清楚,但主要是有一个局部变量,严格用于确保我们在订阅新类上的事件之前取消订阅事件。我将其视为:我们实现了皮肤化的策略模式(选择您想要使用的皮肤策略并使用它运行),但每个策略实现都有我们直接订阅的事件。当策略发生变化时,我们希望订阅者观看正确的发布者,以便我们使用本地人。我再次认为这是一种糟糕的方法。

我建议使用管理器监控其管理的类的所有事件并将其传递以使策略可以更改并且订阅者继续侦听正确的事件通知,从而提出了转换的名称吗?所提供的代码是在我编写问题时动态创建的,因此请原谅任何错误。

3 个答案:

答案 0 :(得分:1)

种类繁多,用户负责切换。那不太好。

交换皮肤时,旧皮肤可能会删除其事件订阅者,也可能将它们附加到新皮肤上。

但是,整洁的模式可能是皮肤持有者,不会改变并暴露事件。

答案 1 :(得分:1)

通常,当您要为触发事件的类创建代理(包装器)时,您需要取消订阅(分离)前一个实例,与新实例交换,然后订阅(附加)到其事件。 / p>

假设您的皮肤界面如下所示:

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
}

您的表单应仅提及IChangeableSkin的实现。

答案 2 :(得分:0)

SkinnedForm可以具有ISkin类型的属性 -

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

通过公共财产公开,并在任何时候设置它。这样,SkinnedForm从不关心ISkin如何工作,或者它包含的事件模型。传入新的Skin类引用时,新的OnBlink事件将自动接管。实现ISkin的类应该包含OnBlink的逻辑。

然后你有一个经理类(不是你指定的那么远),它可以获得对新皮肤的引用,以及相关的SkinnedForm。管理器的唯一工作是更新SkinnedForm上的ISkin属性。