Паттерн MVP и Unity

MVP – Model View Presenter – паттерн организации PL (presentation layer – уровень представления).

MVP применяется при создании десктопных интерфейсов. Выделяют три комопнента: есть модель – группа классов, которые отдают данные или получают команды, представление – форма обладающая состоянием и некоторым поведением. Презентер создают для отделения бизнес-логики от деталей GUI-фреймворка. В отличие от MVC в MVP представление определяет презентер, а не наоборот.

MVP обычно строится вокруг существующих GUI-фреймворков. На практике существуют две принципиально различные различные реализации паттерна – Supervising Controller и Passive View.
В первом случае логика помещается в обработчики событий button_click, а сами обработчики помещаются в отдельный класс. Для полной изоляции презентера от деталей представления надо писать достаточно много врапперов\адаптеров.
Во втором случае создается пара интерфейсов для общения между представлением и презентером. При совершении какого-либо действия представление напрямую обращается к презентеру, тот выполняет некоторый код и вызывает установку свойств представления. Passive View способствует максимальному перемещению кода в в презентер, что облегчает тестирование.

Создание MVP в WinForms с помощью Unity.

Создаем новое winforms приложение и грохаем оттуда форму.

Для начала определим служебные интерфейсы.

//Маркерный интерфейс представления
public interface IView
{
}

//Интрефейс презентера
public interface IPresenter<T> where T:IView
{
    T View { get; set; }
}

//Базовый класс для презентера
public abstract class BasePresenter<T> :IPresenter<T> where T:IView
{
    public T View { get; set; }
}

Класс “бизнес-логики” будем использовать тот же, что и в предыдущем посте.

public interface ISayHelloService
{
    string SayHello(string name);
}
 
public class SayHelloSerivce : ISayHelloService
{
    public string SayHello(string name)
    {
        return "Привет, " + name;
    }
}

Теперь определим рабочие интерфейсы.

public interface ISayHelloPresenter : IPresenter<ISayHelloView>
{
    void SayHello();
}

public interface ISayHelloView: IView
{
    string GetInputText();
    void SetOutputText(string text);
}
Для перезнтера код будет тривиальный.
public class SayHelloPresenter : BasePresenter<ISayHelloView>, ISayHelloPresenter
{
    ISayHelloService _service;

    public SayHelloPresenter(ISayHelloService service)
    {
        this._service = service;
    }

    public void SayHello()
    {
        this.View.SetOutputText(_service.SayHello(this.View.GetInputText()));
    }
}
Теперь нарисуем простую формочку:

Form1

В коде напишем следеющее:

public partial class Form1 : Form, ISayHelloView
{
    ISayHelloPresenter _presenter;

    public Form1(ISayHelloPresenter presenter)
    {
        InitializeComponent();
        _presenter = presenter;
        //Циклическая зависимость
        _presenter.View = this; 
    }

    public string GetInputText()
    {
        return textBox1.Text;
    }

    public void SetOutputText(string text)
    {
        textBox2.Text = text;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        _presenter.SayHello();
    }
}

Циклическая зависимость в Passive View не позволяет с помощью контейнера пропихнуть все зависимости. Поэтому передача презентеру ссылки на представление делается в коде view.

Теперь чтобы увязать это вместе надо создать и сконфигурировать контейнер. Сделаем это прямо в Program.cs.

static void Main()
{
    var container = new UnityContainer();
    container
        .RegisterType<ISayHelloService, SayHelloSerivce>()
        .RegisterType<ISayHelloPresenter, SayHelloPresenter>()
        .RegisterType<ISayHelloView, Form1>()
        ;
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run((Form)container.Resolve<ISayHelloView>());
}

Вот и все.



Unity + ASP.NET MVC

Недавно на хабре появилась статья о Unity. В одном из комментов предложили связать ASP.NET MVC и Unity.

А вот есть прикольная задачка уже не для начинающих — связать ASP.NET MVC с Unity. Требований в целом три:

— обеспечить связывание инфраструктуры контроллеров MVC с инъектором
— модульность
— уникальность контейнера в разрезе сессии

Я бы расписал все, но блин, не успеваю вообще ничего, кроме работы. А вам в рамках обучающих методик будет полезно, имхо.

хабраюзер acerv

Я напишу серию постов о применении Unity в паттернах уровня представления.

Сначала о том что такое MVC.

MVC – Model View Controller – паттерн организации PL (presentation layer – уровень представления). Целью этого паттерна, как и многих других, служит отделение модели (логики работы программы) от представления (средств отображения информации). В итоге применение такого паттерна должно приводить к улучшению тестируемости кода.

В современном виде MVC применяется в вебе. Выглядит так: есть модель – группа классов, которые отдают данные или получают команды, есть различные представления этих данных – HTML, сделанные с помощью какого-либо шаблонизатора, JSON, SOAP, XML или еще какой – либо. Для того чтобы передавать данные от модели к представления вводят контроллер.

Все MVC фреймворки проектируются так, чтобы управление приходило сразу на контроллер. Контроллер вызывает методы модели, если нужно формирует данные для передачи представлению (эти данные называют Presentation Entity, но термин неустоявшийся), и выбирает представление для этих данных.

ASP.NET MVC – фреймворк для ASP.NET, реализующий паттерн MVC.
Контроллеры в ASP.NET MVC – классы, унаследованные от класса Controller, содержащие несколько методов - “действий”  (actions). По умолчанию действия отображаются на различные urlы см помощью механизма раутинга (System.Web.Routing).
Каждое действие возвращает ActionResult, который может вызывать генерацию HTML, сериализацию данных в JSON итд. Можно писать свои ActionResult.

Применение Unity в ASP.NET MVC.

Инфраструктура ASP.NET MVC получает имя контроллера из параметров запроса, формируемых механизмом раутинга, потом вызывает класс ControllerFactory, который по имени возвращает экземпляр контроллера.

Сначала создадим новое asp.net mvc приложение. Оно уже включает в себя большой функционал, его трогать не будем.

Чтобы подключить Unity к механизму создания контроллеров надо написать свой класс ControllerFactory.  По умолчанию используется DefaultControllerFactory, нам надо изменить в нем один метод, который создает объект контроллера.

public class UnityControllerFactory: DefaultControllerFactory
{
    IUnityContainer _container;

    public UnityControllerFactory(IUnityContainer container)
    {
        this._container = container;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        if (controllerType == null)
        {
            return null;
        }

        return (IController)_container.Resolve(controllerType);
    }
}

Теперь в Global.asax добавим следующий код.

protected void Application_Start()
{
    var container = new UnityContainer();
    RegisterControllerFactory(container);
    //...
}

private static void RegisterControllerFactory(IUnityContainer container)
{
    var factory = new UnityControllerFactory(container);
    ControllerBuilder.Current.SetControllerFactory(factory);
}

Жмем F5 и все работает! В принципе для успешного применения unity в asp.net mvc этого достаточно.

Собственно инъекция зависимостей.

Буду использовать один класс “бизнес-логики” во всех примерах.

public interface ISayHelloService
{
    string SayHello(string name);
}

public class SayHelloSerivce : ISayHelloService
{
    public string SayHello(string name)
    {
        return "Привет, " + name;
    }
}

Надеюсь комментарии не нужны.

Снова дополним global.asax.

protected void Application_Start()
{
    var container = new UnityContainer();
    RegisterTypes(container);
    RegisterControllerFactory(container);
    //...
}

private static void RegisterTypes(IUnityContainer container)
{
    container
        .RegisterType<ISayHelloService, SayHelloService>();
}

Теперь у нас будут подпихиваться зависимости, осталось создать контроллер куда они будут подпихиваться.

public class SayHelloController : Controller
{
    ISayHelloService _service;

    public SayHelloController(ISayHelloService service)
    {
        this._service = service;
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Index()
    {
        return View();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Index(string name)
    {
	//Преобразование к object нужно чтобы строка 
	//не была принята за имя View
        return View((object)_service.SayHello(name));
    }
}

Создаем View с типом string, и помещаем в MainContent незамысловатый код:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%using (Html.BeginForm()){%>
    Введите имя <%=Html.TextBox("name")%>
    <br />      
    <input type="submit" value="Сказать привет" />
<%} %>

<%if (!string.IsNullOrEmpty(Model)){%>    
    <h3><%=Html.Encode(Model)%></h3>      
<%} %>
</asp:Content>

Теперь жмем F5, переходим о адресу http://localhost:<порт>/SayHello и смотрим как работает.



MEF

MEF – Managed Extensibility Framework – новая библиотека для создания композитных приложений. То есть приложений, которые собираются из отдельных частей.

Скачать его можно по адресу http://mef.codeplex.com/, версия на момент написания поста – Preview 5. На базе MEF построена Visual Studio 2010.

MEF по функциональности похожа на IoC-контейнеры, но авторы не стремились повторить функциональность существующих контейнеров. В MEF есть несколько уникальных фич, которых нету в  IoC-контейнерах.

Сразу примеры, снова поиск фильмов. Возьмем основной код из поста про Unity.

// Фильм
public class Movie
{
    public string Title { get; set; }
    public string Director { get; set; }
    public int Year { get; set; }
}

/// <summary>
/// Интерфейс репозитария
/// </summary>
public interface IMovieRepository
{
    IQueryable<Movie> GetMovies();
}

// Сервис поиска фильмов
[Export]
public class MovieFinder
{
    IMovieRepository _repository;

    [ImportingConstructor]
    public MovieFinder(IMovieRepository repository)
    {
        _repository = repository;
    }

    public IEnumerable<Movie> FindByTitle(string q)
    {
        return _repository
                .GetMovies()
                .Where(m => m.Title.Contains(q));
    }
}

// Заглушка для репозитария
[Export(typeof(IMovieRepository))]
public class InMemoryMovieRepository : IMovieRepository
{
    public IQueryable<Movie> GetMovies()
    {
        return new[]   
        {   
            new Movie   
            {   
                Title = "Гарри Поттер и узник Азкабана",   
                Director = "Альфонсо Куарон",   
                Year = 2004   
            },   
            new Movie   
            {   
                Title = "Звездные войны: Эпизод 2 - Атака клонов",   
                Director = "Джордж Лукас",   
                Year = 2002   
            },   
            new Movie   
            {   
                Title = "Властелин колец: Братство кольца",   
                Director = "Питер Джексон",   
                Year = 2001   
            },   
        }.AsQueryable();
    }
}  

В коде атрибутами размечены экспортируемые классы и импортирующий конструктор.

Теперь код, который это все использует.

static void Main(string[] args)
{
    //Создание каталога "частей"
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

    //Создание контейнера
    var container = new CompositionContainer(catalog);

    //Получение экземпляра
    var finder = container.GetExportedObject<MovieFinder>();
}

В текущем виде мало отличается от примера с Unity.

На самом деле в MEF гораздо более широкие возможности работы с импортами и экспортами. Экспортировать можно не только классы, но и методы, и значения свойств. Импортировать можно не только через конструктор, но и через свойства и даже через приватные поля (но это не будет работать в частично доверенном окружении).

Упростим пример, класс Movie и Main останутся без изменений.

[Export]
public class MovieFinder
{
    [Import]
    Func<IQueryable<Movie>> _getMovies = null;

    public IEnumerable<Movie> FindByTitle(string q)
    {
        return _getMovies().Where(m => m.Title.Contains(q));
    }
}

public class InMemoryMovieRepository
{
    [Export(typeof(Func<IQueryable<Movie>>))]
    public IQueryable<Movie> GetMovies()
    {
        return new[]   
        {   
            //...
        }.AsQueryable();
    }
}

Также важной фичей MEF является возможность поиска частей в сборках в каталоге на диске, а также мониторинг изменений этих сборок и перезагрузка частей. Кроме того есть API для пересборки частей при изменении.

Но об этом в другой раз.



Нельзя построить коммунизм на ESB

SOA с помощью IoC – это практически коммунизм. В точности как гласит лозунг “от каждого по возможностям, каждому - по потребностям”. Потребности – зависимости классов, возможности – реализуемые интерфейсы, а ЦК КПСС – IoC-контейнер.

В архитектуре с шиной сообщений (ESB) сервисы не общаются друг с другом, а только с шиной, живут в изоляции. Это не коммунизм, а тирания.



Плачь по SOA

Наверное все слышали о SOA, но у большинства эти три буквы ассоциируются исключительно с ws-* вебсервисами. Хотя на самом деле это очень хорошая концепция построения программной системы из компонент, абсолютно не привязанная к способам реализации взаимодействия.

Принцип SOA состоит в следующем. Вся система разбивается на слабо связные компоненты – сервисы. Сервисы знают друг о друге только контракт. Сервисы в SOA, в отличие от объектов в ООП, не обладают свойством идентичности. Так как сервисы не знают ничего друг о друге, то должен быть общеизвестный сервис, который по контракту может выдать ссылку на сервис.

Так как сервисы не обладают идентичностью, а все ссылки на сервисы с одним контрактом равнозначны, то это позволяет динамически изменять систему, подменять один компоненты другими и достаточно просто балансировать нагрузку.

В мире ws-* вебсервисов контракты реализуются с помощью WSDL, а общеизвестными сервисами являются брокеры, которые по WSDL могут возвращать ссылки на сервисы. Сейчас наблюдается практически полное отсутствие ws-брокеров на просторах интернета. WSDL не описывает семантику, а только синтаксис, поэтому просто получить ссылку на сервис не получится, неизвестно вообще говоря что этот сервис делает. Кроме того нету стандартных контрактов, каждый вендор делает такой контракт как захочется. Это все приводит к тому что идея брокеров на практике оказывается нежизнеспособной. В большинстве библиотек вебсервисов не реализована поддержка UDDI – протокола общения с брокерами. Глобального коммунизма не будет.

А вот в отдельно взятой стране программе такой коммунизм построить можно. Роль контрактов будут играть интерфейсы языка, роль сервисов – объекты, реализующие интерфейсы, а в качестве брокера будет выступать IoC-контейнер. Он как раз умеет по интерфейсу отдавать ссылку на сервис, а его возможность собирать зависимости позволит избавить сами сервисы от общения с брокером.

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