Как управлять подписками на сменные классы - PullRequest
2 голосов
/ 08 июня 2011

Я пытаюсь лучше понять, как сохранить подписку на класс, который может поменяться (изменить стратегию).Я постараюсь сохранить это, даже если примеры надуманы.

Предположим, что существует класс Skin

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

Я не уверен, что это ясно, но в основном есть локальная переменная, которая используется строго для того, чтобы мы отписались от событий перед подпискойсобытия в новом классе.Я рассматриваю это следующим образом: мы реализовали шаблон стратегии создания скинов (выберите стратегию скинов, которую вы хотите использовать, и запустите ее), но у каждой реализации стратегии есть события, на которые мы напрямую подписываемся.Когда стратегия меняется, мы хотим, чтобы наши подписчики следили за правильным издателем, поэтому мы используем местных жителей.Опять же, я думаю, что это ужасная методология.

Есть ли название для преобразования, которое я предложил, используя менеджера для отслеживания всех событий класса, которым он управляет, и передачи их таким образом, чтобы стратегия могла измениться иподписчики продолжают слушать правильные уведомления о событиях?Предоставленный код был создан на лету, когда я сформулировал вопрос, так что извините за любые ошибки.

Ответы [ 3 ]

1 голос
/ 08 июня 2011

Как правило, когда вы хотите создать прокси (обертку) для класса, который запускает события, вам необходимо отменить подписку (отсоединить) предыдущий экземпляр, заменить его новым, а затем подписаться (присоединить) к его событиям.

Допустим, ваш интерфейс скина выглядит следующим образом:

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.

1 голос
/ 08 июня 2011

Вид сложный и бремя переключения ложится на абонента. Это не так хорошо.

При смене скинов старый скин может удалить своих подписчиков на события и, возможно, присоединить их к новому скину.

Но более аккуратным рисунком может быть скин-держатель, который не меняется и выставляет события.

0 голосов
/ 08 июня 2011

SkinnedForm может иметь свойство типа ISkin -

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

Предоставить это через открытое свойство и установить его в любой точке.Таким образом, SkinnedForm никогда не заботится о том, как работает ISkin, или о модели событий, которую он содержит.Когда вы передаете новую ссылку на класс Skin, новое событие OnBlink автоматически вступает во владение.Классы, реализующие ISkin, должны содержать логику для OnBlink.

Затем у вас есть класс менеджера (не слишком далеко от того, что вы указали), который может получить ссылку на новый скин и соответствующий SkinnedForm.Единственная задача менеджера - обновить свойство ISkin в SkinnedForm.

...