Страницы

суббота, 20 августа 2011 г.

Реализуем EventPublisher. Шаг второй.

В прошлой части я наметил основу будущего диспетчера событий. Пора заняться реализацией.
Для начала определим стоящие перед нами проблемы. К счастью их всего две:
  1. Получение списка подписчиков сообщений. 
  2. Отправка сообщений подписчикам. 
Решение обоих задач крайне желательно сделать легко модифицируемым и расширяемым. Поэтому их реализация начинается... с написания новых интерфейсов. Для каждой из задач оответсвенно:

public interface IListenerSource
{
  IEnumerable<IListener<TMessage>> ResolveListenersFor<TMessage>();
}

public interface IPublishWay
{
  void Publish<TMessage>(TMessage message, IListener<TMessage> listener);
}
Эти интерфейсы открывают путь к простейшей реализации диспетчера событий:
public class Publisher : IPublisher
{
  protected readonly IListenerSource ListenerSource;
  protected readonly IPublishWay PublishWay;

  public Publisher(IListenerSource listenerSource, IPublishWay publishWay)
  {
    if(listenerSource == null)
      throw new ArgumentNullException("listenerSource");
    if(publishWay == null)
      throw new ArgumentNullException("publishWay");

    ListenerSource = listenerSource;
    PublishWay = publishWay;
  }

  public virtual void Publish<TMessage>(TMessage message)
  {
    foreach (var listener in ListenerSource.ResolveListenersFor<TMessage>())
    {
      PublishWay.Publish(message, listener);
    }
  }
}

Теперь можно перейти к реализации IPublishWay. В простейшем случае можно сделать так:
public class SimplePublishWay : IPublishWay
{
  public virtual void Publish<TMessage>(TMessage message, IListener<TMessage> listener)
  {
    listener.ListenTo(message);
  }
}
Для работы в реальных условиях такая реализация вряд ли годится. Но пригодится для тестировочных конфигураций Publisher'a. Но об этом в следующей части. А сейчас посмотрим на более полезную реализацию, с использованием SynchronizationContext. От просто SynchronizationContext пользы немного, но при использовании его наследника WindowsFormsSynchronizationContext мы получим маршаллинг событий в поток пользовательского интерфейса. Подробнее об это можно почитать например здесь. Опять же, простейшая реализация:
public class SynchronizationContextPublishWay : IPublishWay
{
  private readonly SynchronizationContext _synchronizationContext;

  public SynchronizationContextPublishWay(SynchronizationContext synchronizationContext)
  {
    _synchronizationContext = synchronizationContext;
  }

  public void Publish<TMessage>(TMessage message, IListener<TMessage> listener)
  {
    _synchronizationContext.Post(x => listener.ListenTo(message), null);
  }
}
Метод Post позволит реализовать асинхронную передачу сообщений, освободив вызывающий поток для другой работы.
Остался последний штрих - реализовать IListenerSource. Для первой реализации можно взять обыкновенный список. Но лучше воспользоваться новой коллекцией из .net 4 - ISet. Она не позволяет дважды добавить один и тот же объект, что избавит от необходимости предварительной проверки.
public class SimpleAssignee : IAssignee
{
  private readonly ISet<object> _listeners;
  private readonly object _latch;

  public SimpleAssignee()
  {
    _listeners = new HashSet<object>();
    _latch = new object();
  }

  public virtual void Subscribe(object listener)
  {
    lock(_latch)
    {
      _listeners.Add(listener);
    }
  }

  public virtual void Unsubscribe(object listener)
  {
    lock(_latch)
    {
      _listeners.Remove(listener);
    )
  }

  public virtual IEnumerable<IListener<TMessage>> ResolveListenersFor<TMessage>()
  {
    lock(_latch)
    {
      return _listeners.OfType<IListener<TMessage>>().ToList();
    }
  }
}

На этом простая реализация диспетчера сообщений завершена, можно конфигурировать и использовать. Но это еще не все... В следующей части рассмотрим вопросы тестирования.
Код проекта по-прежнему можно найти на github

Комментариев нет:

Отправить комментарий