Абстракция хорошо, но важно помнить, что в какой-то момент то должен знать вещь или два о вещи или два, или же мы просто есть куча красиво абстрагированных legos, сидящих на полу вместо того, чтобы их собирали в дом.
Контейнер с инверсией управления/зависимой инъекцией/flippy-dippy-upside-down-whatever-we're-call-it-this-week, такой как Autofac, действительно может помочь собрать все это вместе.
Когда я сбрасываю приложение WinForms, я обычно получаю повторяющийся шаблон.
Начну с Program.cs
файла, который конфигурирует контейнер Autofac, а затем извлекает экземпляр MainForm
из него, и показывает MainForm
.Некоторые люди называют это оболочкой или рабочей областью или рабочим столом, но, во всяком случае, это «форма», которая имеет панель меню и отображает либо дочерние окна, либо дочерние элементы управления пользователя, а когда она закрывается, приложение завершает работу.
Следующий приведенный выше номер MainForm
. Я делаю основные вещи, такие как перетаскивание некоторых SplitContainers
и MenuBar
s и таких в визуальный конструктор Visual Studio, а затем я начинаю получать фантазии в коде: у меня будут определенные интерфейсы ключей, «введенные» в конструктор MainForm
так что я могу их использовать, так что мой MainForm может организовывать дочерние элементы управления, не имея при этом особого знания о них.
Например, у меня может быть интерфейс IEventBroker
, который позволяет различным компонентам публиковать или подписываться на «события», такие как BarcodeScanned
или ProductSaved
. Это позволяет частям приложения реагировать на события свободно связанным образом, не полагаясь на проводку традиционных событий .NET. Например, EditProductPresenter
, который вместе с моим EditProductUserControl
может сказать this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
, и IEventBroker
проверит список своих подписчиков на это событие и вызовет их обратные вызовы. Например, ListProductsPresenter
может прослушивать это событие и динамически обновлять ListProductsUserControl
, к которому он присоединен. Конечным результатом является то, что если пользователь сохраняет продукт в одном пользовательском элементе управления, другой ведущий органа управления пользователя может реагировать и обновлять себя, если он окажется открытым, без какого-либо контроля, который должен знать о существовании друг друга, и без MainForm
, имеющих организовать это событие.
Если я проектирования приложения MDI, я мог бы иметь MainForm
реализовать IWindowWorkspace
интерфейс, который имеет Open()
и Close()
методы. Я мог бы вставить этот интерфейс в мои различные презентации, чтобы они могли открывать и закрывать дополнительные окна, не зная непосредственно о MainForm
. Например, ListProductsPresenter
может захотеть открыть EditProductPresenter
и соответствующий EditProductUserControl
, когда пользователь дважды щелкает строку в сетке данных в ListProductsUserControl
. Он может ссылаться на IWindowWorkspace
, который на самом деле является MainForm
, но ему не нужно это знать - и звоните Open(newInstanceOfAnEditControl)
и предположите, что элемент управления был показан в соответствующем месте приложения. (The MainForm
реализация будет, по-видимому, поменять управление в поле зрения на панели где-нибудь.)
Но как, черт бы ListProductsPresenter
создать этот экземпляр EditProductUserControl
? Autofac's delegate factories являются истинная радость здесь, так как вы можете просто вводить делегат в ведущий и Autofac будет автоматически подключать его, как если бы это был завод (псевдокод следует):
public class EditProductUserControl : UserControl
{
public EditProductUserControl(EditProductPresenter presenter)
{
// initialize databindings based on properties of the presenter
}
}
public class EditProductPresenter
{
// Autofac will do some magic when it sees this injected anywhere
public delegate EditProductPresenter Factory(int productId);
public EditProductPresenter(
ISession session, // The NHibernate session reference
IEventBroker eventBroker,
int productId) // An optional product identifier
{
// do stuff....
}
public void Save()
{
// do stuff...
this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
}
}
public class ListProductsPresenter
{
private IEventBroker eventBroker;
private EditProductsPresenter.Factory factory;
private IWindowWorkspace workspace;
public ListProductsPresenter(
IEventBroker eventBroker,
EditProductsPresenter.Factory factory,
IWindowWorkspace workspace)
{
this.eventBroker = eventBroker;
this.factory = factory;
this.workspace = workspace;
this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
}
public void WhenDataGridRowDoubleClicked(int productId)
{
var editPresenter = this.factory(productId);
var editControl = new EditProductUserControl(editPresenter);
this.workspace.Open(editControl);
}
public void WhenProductSaved(object sender, EventArgs e)
{
// refresh the data grid, etc.
}
}
Так ListProductsPresenter
знает о Edit
функции set (т. е. редактор редактирования и пользовательский элемент управления редактирования) - и это прекрасно, они идут рука об руку - но ему не нужно знать обо всех зависимостях набора функций Edit
, вместо этого полагаясь на делегата, предоставленного Autofac, чтобы разрешить все эти зависимости для него.
Как правило, я считаю, что у меня есть взаимно однозначное соответствие между «презентатором/представлением модели/контролирующим контроллером» (давайте не будем слишком сильно разбираться в различиях, так как в конце дня все они очень похожи) и "UserControl
/Form
". UserControl
принимает модель/контроллер презентатора/представления в своем конструкторе и в самих файлах данных, насколько это уместно, откладывая как можно больше докладчика.Некоторые люди скрывают UserControl
от ведущего через интерфейс, например IEditProductView
, что может быть полезно, если представление не является полностью пассивным. Я стараюсь использовать привязку данных для всего, так что общение осуществляется через INotifyPropertyChanged
и не беспокойтесь.
Но вы сделаете свою жизнь намного легче, если ведущий бесстыдно привязан к виду. Является ли свойство в вашей объектной модели не привязанным к привязке данных? Выставляйте новое свойство, чтобы оно было. Вы никогда не будете иметь EditProductPresenter
и EditProductUserControl
с одним макетом, а затем хотите написать новую версию пользовательского элемента управления, которая работает с тем же ведущим. Вы просто отредактируете их оба, они предназначены для всех целей и целей одного устройства, одна функция, презентатор существует только потому, что она легко тестируется на единицу и пользовательский элемент управления не является.
Если вы хотите, чтобы функция была заменяемой, вам необходимо абстрагироваться от всей функции как таковой. Таким образом, у вас может быть интерфейс INavigationFeature
, с которым разговаривает MainForm
. У вас может быть TreeBasedNavigationPresenter
, который реализует INavigationFeature
и потребляется TreeBasedUserControl
. И у вас может быть CarouselBasedNavigationPresenter
, который также реализует INavigationFeature
и потребляется CarouselBasedUserControl
. Пользователь контролирует и ведущие по-прежнему идут рука об руку, но ваш MainForm
не должен заботиться о том, взаимодействует ли он с древовидным представлением или с карусельной версией, и вы можете заменить их без MainForm
, являющегося мудрее.
В заключение, легко сбить с толку. Все педантичны и используют немного другую терминологию, чтобы передать их тонкие (и часто неважные) различия между тем, что аналогично архитектурным узорам. По моему скромному мнению, инъекция зависимостей делает чудеса для создания композиционных, расширяемых приложений, поскольку сцепление сохраняется; разделение функций на «ведущие/просмотр моделей/контроллеров» и «представления/пользовательские элементы управления/формы» делает чудеса качества, поскольку большинство логических элементов втягивается в первое, что позволяет легко тестировать его; и объединение двух принципов, по-видимому, действительно то, что вы ищете, вы просто путаетесь с терминологией.
Или, я могу быть полон его. Удачи!
Спасибо за ответ. Я не пытаюсь погрузиться в философский жаргон, который может быть вдохновлен разговорами о шаблонах дизайна, но я пытаюсь получить некоторые технические знания под моим поясом. Таким образом, этот пассивный взгляд действительно усугубляет меня. Как я могу создать с нуля мастер-представление с субвью внутри него (чтобы весь основной вид был взаимозаменяемым, а также любой из подзонов внутри главного представления)? Я не могу найти ничего в сети, чтобы правильно решить эту проблему (возможно, никто еще не решил ее окончательно?). – 2010-12-02 16:38:51
Этот ответ более 6 лет, и все же это одно из лучших объяснений практического использования MVP, которое я нашел в Интернете. Спасибо, это невероятно образовательно, даже если программирование WinForms стало несколько устаревшим в наши дни. – Tarec 2017-04-24 11:00:10