Страницы

четверг, 8 сентября 2011 г.

Model-View-Presenter - два пути создания презентеров.

Проблема.

  Пусть у нас есть WinForms приложение, созданное с применением шаблона Model-View-Presenter. Логичным шагом будет наличие главного презентера, реализующим логику работу главной формы. И одна из главных обязанностей - создание и отображение дочерних презентеров. Звучит просто, но в реализации возникает главная сложность: как создавать дочерние презентеры? Я бы принял во внимание два возможных варианта действий. Оба из них предполагают использования шаблона Ioc/DI, и любого контейнера на ваш вкус.
  Рассмотрим нашего подопытного:
public class MainPresenter
{
  public void ShowClients()
  {
    //как создать и отобразить презентер?
  }

  public void ShowVendors()
  {
    //как создать и отобразить презентер?
  }

  // Много других аналогичных методов.

}

Вариант 1: Абстрактная фабрика.

  Название как-бы намекает нам на абстрактный класс, но лучше пропустить этот намёк и сделать интерфейс:
public interface IPresenterFactory
{
   IPresenter CreateClientsPresenter();

   IPresenter CreateVendorsPresenter();

   // Создание остальных видов презентеров
}
И тогда код презентера обретает вид:
public class MainPresenter
{
  public MainPresenter(IPresenterFactory presenterFactory)
  {
    _presenterFactory = presenterFactory;
  }

  public void ShowClients()
  {
    var presenter = _presenterFactory.CreateClientsPresenter();
    presenter.Start();
  }

  public void ShowVendors()
  {
    var presenter = _presenterFactory.CreateVendorsPresenter();
    presenter.Start();
  }

  // Много других аналогичных методов.
}
Еще одна вариация на тему - обобщённая версия абстрактной фабрики:
public interface IPresenterFactory
{
   IPresenter CreatePresenter<TPresenter>() where TPresenter : IPresenter;
}
Замечу, возвращаемое значение  IPresenter. Возвращать TPresenter крайне нежелательно, поскольку мы сильно затрудним тестирование. У конкретных реализаций презентеров могут быть зависимости, у которых в свою очередь могут быть зависимости... Создать модульный тест в таком случае будет почти невозможно.  Для обобщённой фабрики презентер будет выглядеть так:
public class MainPresenter
{
  public MainPresenter(IPresenterFactory presenterFactory)
  {
    _presenterFactory = presenterFactory;
  }

  public void ShowClients()
  {
    var presenter = _presenterFactory.Create<ClientsPresenter>();
    presenter.Start();
  }

  public void ShowVendors()
  {
    var presenter = _presenterFactory.Create<VendorsPresenter>();
    presenter.Start();
  }

  // Много других аналогичных методов.
}
Теперь окинем код взглядом еще раз, и оценим достоинства.
Необобщённая версия:
  • + Конкретная реализации презентеров скрыта.
  • + Возможные варианты создаваемых презентеров явно видны. 
  • + В создающие методы можно добавить параметры.
  • - Много однообразного кода.
Обобщённая версия:
  • + "Халявная" поддежка новых типов презентеров. 
  • + Экономия кода. 
  • - Нарушение принципов сокрытия информации и OCP
Возможен и гибридный вариант, помесь обобщённой и необобщённой фабрик. В таком случае их достоинства и недостатки так же объединятся, и к ним добавиться еще один недостаток: неоднозначность выбора метода создания презентера.

Вариант 2: Диспетчер событий.

Второй способ я уже описывал в прошлом посте. Но для ясности повторю идею. Если у нас есть Event Aggregator:
public interface IPublisher
{
  void Publish<TMessage>(TMessage message);
}
Чтобы использовать диспетчер событий, надо создать классы событий:
public class ShowClientsMessage{}

public class ShowVendorsMessage{}
И классы обработчики событий:
public class ShowClientsMessageListener: IListener<ShowClientsMessageListener>
{
  public ShowClientsMessageListener(ClientsPresenter presenter)
  {
    _presenter = presenter;
  }

  public void ListenTo(ShowClientsMessage message)
  {
     presenter.Start();
  }
}
// второй класс не показан для краткости. 
То с их помощью можно значительно упростить главный презетер:
public class MainPresenter
{
  public MainPresenter(IPublisher publisher)
  {
    _publisher = publisher;
  }

  public void ShowClients()
  {
    _publisher.Publish(new ShowClientsMessage());

  }

  public void ShowVendors()
  {
    _publisher.Publish(new ShowVendorsMessage());
  }

  // Много других аналогичных методов.
}
Детали реализации можно найти в предыдущих постах. А сейчас немного о преимуществах и недостатках:
  • + Скрыта не только конкретная реализация презентеров, но способы их создания и отображения! Это даёт огромную гибкостью 
  • + Через классы сообщений легко передавать параметры.
  • + Высокая тестируемость главного презентера и обработчиков событий.  
  • - Требуется больше намного больше кода.
  • - Менее очевидны зависимости

AbstractFactory vs EventAggregator. 

  Что же выбрать? Этот вопрос традиционно переходит в вопрос гибкость против сложности.
Абстрактная фабрика проста в реализации, ее легко отлаживать. В то же время сокрытие информации с диспетчером событий даёт фантастическую гибкость. Я предпочитаю гибкость диспетчера событий, т.к. набрать немного простого кода не так сложно, а поддерживать его намного проще.
  Стоит отметить, что грамотное использование современных контейнеров (и точек их расширения) позволяет существенно уменьшить объёмы ручного труда в обоих вариантах. Но об этом позже.

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

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