Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GEK / ООП_ГОСЫ_ОТВЕТЫ.docx
Скачиваний:
68
Добавлен:
18.05.2015
Размер:
1.83 Mб
Скачать

Недостатки mvc и Document-View

В целом MVC и Document-View, как его логическое продолжение вполне адекватные паттерны для ряда задач, и с успехом применяются в большем количестве проектов, но в то же время обладают и рядом недостатков.

Прежде всего, чистый MVC плохо подходит для сред типа WinForms, поскольку, как уже говорилось выше, код, который порождают среды и библиотеки провоцирует интегрировать Контроллер в Представление. Получающийся в результате вариант паттерна в виде Document-View, в принципе, вполне приемлемое решение, однако далеко не всегда. Дело в том, что логика Контроллера, на самом деле, практически не зависит от типа Представления и сама по себе довольно неплохо поддается тестированию. А встраивание этой логики в Представление, в случае разных типов Представлений, приводит к дублированию кода и практически исключает возможность внятного тестирования этой логики, так как тестирование пользовательского интерфейса – это отдельная головная боль. Да и в целом, реализация логики контроллера вместе с представлением порождает довольно кашеобразную конструкцию из смеси автогенеренного и ручного кода Представления и кода логики Контроллера, который довольно сложно поддерживать. С появлением FW 2.0 и Partial-классов стало несколько легче, но проблема все равно актуальна.

Если же попытаться выделить Контроллер «в лоб», то можно нарваться на следующие неприятности...

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

Далее, выделя контроллер в его классическом варианте, можно столкнуться с недостатком связанным с особенностью роли Модели в классическом MVC. Как уже говорилось, MVC – это не просто паттерн, а набор паттернов, и Модель, в классическом MVC, на самом деле является медиатором (Mediator) между Контроллером/Представлением и реальной моделью домена (Domain Model) приложения (как это отражено на схеме).

СОВЕТ

Паттерн Mediator (Медиатор), заключается в реализации единого объекта, который скрывает взаимодействие группы объектов между собой. Предназначен для уменьшения связности и сложности системы, за счет того, что с группой объектов можно работать как с единым целым.

Более подробно с паттерном можно ознакомится здесь: http://en.wikipedia.org/wiki/Mediator_pattern и здесь: http://dofactory.com/Patterns/PatternMediator.aspx

В обязанности Медиатора входит транслировать вызовы Контроллера в нужные модели приложения и реализовать механизм оповещения Представлений, как правило посредством паттерна Обозреватель (Observer), о том, что нижележащая модель приложения изменилась. Таким образом, Модель должна обладать набором методов реализующих логику работы с пользовательским интерфейсом, однако сама Модель не может влиять напрямую на этот самый интерфейс (представления), в противном случае это убило бы саму идею паттерна, что приводит к необходимости реализации в Представлении логики обработки событий и, как следствие, «утолщению» Представления. Плюс к этому, в некоторых случаях хотелось бы дать пользователю возможность непосредственно влиять на Представление, без привлечения событийного механизма. Например, изменение цвета в некоторых Представлениях в классическом MVC выглядело бы так: в Контроллер попадает событие изменения цвета с кодом цвета, Контроллер вызывает соответствующий метод в Модели, в котором Модель выставляет нужный цвет и бросает событие о том, что в ней произошли изменения, затем данное событие перехватывается Представлениями и Представления перерисовываются, самостоятельно забрав у Модели новый цвет. Сам Контроллер не может отдать команду перерисоваться с нужным цветом, так как не умеет хранить состояние цвета и не знает каким Представлениям нужно передавать эту команду.

Таким образом нам нужна модификация паттерна которая, с учетом упомянутых недостатков, позволяла бы следующее:

  • Умела эффективно отделять модель от ее представлений.

  • Позволяла пользоваться дизайнером форм и имеющимися библиотеками, без ограничений.

  • Позволяла тестировать логику Контроллера независимо от Представления и вообще, сводила бы логику Представления к минимуму.

  • Позволяла бы избегать лишних обращений к Модели.

Одним из возможных решений, отвечающим всем вышеприведенным требованиям, и является паттерн Model-View-Presenter (MVP).

Model-View-Presenter

Паттерн Model-View-Presenter, является очередной модификацией MVC. Если смотреть просто на схему, то визуально отличить его от MVC довольно сложно, поэтому лучше всего иллюстрировать его реализацию на кусочке реального кода. Код, конечно, реальный весьма относительно и довольно примитивен, однако надеюсь, вполне достаточнен для иллюстрации паттерна - мы попробуем реализовать программу, переводящую градусы цельсия в фаренгейты и наоборот, ну и, естественно, визуально это дело отображающую.

Model

Если бы мы разрабатывали наше приложение в привычном стиле, навязываемом WinForms, то скорее всего начали бы с набрасывания формочек, однако в данном случае мы зайдем с другой стороны и начнем с реализации Модели. И так, наша Модель должна уметь принимать данные температуры как в цельсиях так и в фаренгейтах и пересчитывать их в других величинах. Типичная Модель для данной задачи могла бы выглядеть следующим образом:

public class Model

{

private double _valueFahrenheit = 32;

private double _valueCelsius = 0;

/// <summary>

/// Градусы в шкале Фаренгейта

/// </summary>

public double valueFahrenheit

{

get { return _valueFahrenheit; }

set

{

_valueFahrenheit = value;

_valueCelsius = (_valueFahrenheit - 32) * 5 / 9;

}

}

/// <summary>

/// Градусы в шкале Цельсия

/// </summary>

public double valueCelsius

{

get { return _valueCelsius; }

set

{

_valueCelsius = value;

_valueFahrenheit = _valueCelsius * 9 / 5 + 32;

}

}

}

Presenter

Следующим этапом будет реализация Presenter-а, эта сущность заменяет Контроллер в классическом MVC - она делает все тоже самое, что и Контроллер, плюс кое-что еще. Более подробно о его функциях мы поговорим позже.

Presenter может иметь прямую ссылку на экземпляр Модели. В то же время Presenter должен иметь ссылку и на экземпляр или даже экземпляры Представления, которые будут отображать данные Модели. Однако здесь, в отличие от случая с Моделью, создание конкретного экземпляра конкретного класса Представления, может обернуться большими неудобствами. При наличии тесной связи между Presenter-ом и Представлением будет сложно реализовать замену Представлений и использование нескольких Представлений для одного Presenter-а, к тому же, это затруднит независимое тестирование Presenter-а... Да и в целом, наличие такой связи может привести к неправильной работе Presenter-а при изменении Представления, что совсем не хорошо.

Решить проблему зависимости Presenter-а от Представления можно с помощью паттерна Inversion of Control (он же Dependency Injection по Фаулеру) . Для этого мы создаем интерфейс Представления через который и будет осуществляться все взаимодействие с Представлениями.

СОВЕТ

Inversion of Control (IoC), это даже не паттерн, а архитектурный принцип, который используется для уменьшения связности между объектами. Суть его довольно проста... Допустим объект x (класс X) вызывает некий метод объекта y (класс Y), в этом случае считается, что X зависит от Y. Данная зависимость может быть «перевернута» путем введения третьего класса I, называемого интерфейсным классом, который содержит в себе все методы, которые x вызывает у объекта y, при этом Y должен реализовывать интерфейс I. После подобного преобразования X и Y зависят от I, но не зависят друг от друга, более того, если ранее X так же транзитивно зависил от всех классов от которых зависит Y, то теперь и эта зависимость оказалась разорвана.

Более подробно с этим принципом можно ознакомиться здесь: http://en.wikipedia.org/wiki/Inversion_of_control и здесь: http://www.martinfowler.com/articles/injection.html

Интерфейс Представления, в нашем случае, будет состоять из свойств для вывода посчитанных значений градусов цельсия и фаренгейта, ввода новых данных и событий оповещающих Presenter, что данные введены.

public interface IView

{

/// <summary>

/// Вывод градусов Фаренгейта

/// </summary>

void SetFarenheit(double value);

/// <summary>

/// Вывод градусов Цельсия

/// </summary>

void SetCelsius(double value);

/// <summary>

/// Ввод нового значения градусов

/// </summary>

double InputDegree { get; }

/// <summary>

/// Событие ввода значения по Фаренгейту

/// </summary>

event EventHandler<EventArgs> SetFarenheit;

/// <summary>

/// Событие ввода значения по цельсию

/// </summary>

event EventHandler<EventArgs> SetCelsius;

}

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

СОВЕТ

Паттерн Factory Method (фабричный метод), как и другие «строительные» паттерны, предназначен для создания объекта без указания конкретного класса реализующего этот объект. В данном случае это делается путем объявления в самом классе только публичного интерфейса для создания объекта, само же создание делегируется классам-наследникам.

Более подробно об этом паттерне можно почитать здесь: http://en.wikipedia.org/wiki/Factory_method_pattern и здесь: http://dofactory.com/Patterns/PatternFactory.aspx

Наличие интерфейса так же позволяет уже на этом этапе полностью завершить разработку Presenter-а и заняться его тестированием, несмотря на то, что конкретные Представления еще не готовы и ни один контрол еще не брошен на форму... ( И так, Presenter может выглядеть примерно следующим образом:

public class Presenter

{

private Model _model = new Model();

private IView _view;

/// <summary>

/// В конструтор передается конкретный экземпляр представления

/// и происходит подписка на все нужные события.

/// <summary>

public Presenter(IView view)

{

_view = view;

_view.SetCelsius += new EventHandler<EventArgs>(OnSetCelsius);

_view.SetFarenheit += new EventHandler<EventArgs>(OnSetFarenheit);

RefreshView();

}

/// <summary>

/// Обработка события, установка нового значения градусов по Фаренгейту

/// </summary>

private void OnSetFarenheit(object sender, EventArgs e)

{

_model.valueFahrenheit = _view.InputDegree;

RefreshView();

}

/// <summary>

/// Обработка события, установка нового значения градусов Цельсия

/// </summary>

private void OnSetCelsius(object sender, EventArgs e)

{

_model.valueCelsius = _view.InputDegree;

RefreshView();

}

/// <summary>

/// Обновление Представления новыми значениями модели.

/// По сути Binding (привязка) значений модели к Представлению.

/// </summary>

private void RefreshView()

{

_view.SetCelsius(_model.valueCelsius);

_view.SetFarenheit(_model.valueFahrenheit);

}

}

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

View

В данном случае реализовать интерфейс можно в классе реализующем форму, причем сделать это можно в отдельном проекте, вдруг потом захочется реализовать и web-интерфейс? ( Разработка конкретного Представления совсем тривиальна, форма представления наследуется от созданного нами ранее интерфейса, при этом реализация свойств интерфейса примитивна – по сути это просто перекладывание значений из интерфейса в контролы и наоборот.

public partial class FormView : Form, IView

{

#region Реализация IView

/// <summary>

/// Вывод градусов Фаренгейта

/// </summary>

public double Farenheit

{

set { _farenheitBox.Text = value.ToString("N2"); }

}

/// <summary>

/// Вывод градусов Цельсия

/// </summary>

public double Celsius

{

set { _celsiusBox.Text = value.ToString("N2"); }

}

/// <summary>

/// Ввод нового значения градусов

/// </summary>

public double InputDegree

{

get { return Convert.ToDouble(_inputBox.Text); }

}

#endregion

/// <summary>

/// Обработка событий тоже примитивна, они просто пробрасываются

/// в соответствующие события Presenter-а

/// </summary>

private void _celsiusButton_Click(object sender, EventArgs e)

{

if (SetCelsius != null)

SetCelsius(this, EventArgs.Empty);

}

private void _farenheitButton_Click(object sender, EventArgs e)

{

if (SetFarenheit != null)

SetFarenheit(this, EventArgs.Empty);

}

}

Ну и, наконец, завершающий штрих, необходимо создать экземпляр Представления, подпихнуть его в Presenter и запустить всю эту конструкцию на выполнение.

static class Program

{

[STAThread]

static void Main()

{

FormView view = new FormView();

Presenter presenter = new Presenter(view);

Application.Run(view);

}

}

Вот собственно и все, паттерн MVP в своем простейшем варианте перед вами, самое время его обсудить.

Соседние файлы в папке GEK