Вы можете сделать это сотнями способов, чтобы достичь конечной цели разделения интересов. Здесь нет жестких и быстрых правил, основная идея заключается в том, что докладчик имеет дело с логикой представления представления, в то время как представление имеет только тупое знание о своих собственных классах и материалах, специфичных для GUI. Некоторые способы, которыми я могу придумать (в широком смысле):
1) Просматривайте все с нуля и дайте ему решать, как его вести. Вы начинаете как, new View().Start();
// your reusable MVP framework project
public interface IPresenter<V>
{
V View { get; set; }
}
public interface IView<P>
{
P Presenter { get; }
}
public static class PresenterFactory
{
public static P Presenter<P>(this IView<P> view) where P : new()
{
var p = new P();
(p as dynamic).View = view;
return p;
}
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public IEmployeeView View { get; set; } // enforced
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeeView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; } // enforced
public EmployeeView()
{
Presenter = this.Presenter(); // type inference magic
}
public void OnSave()
{
Presenter.Save();
}
}
Вариант вышеупомянутого подхода будет заключаться в применении более строгих общих ограничений для представления и докладчика, но я не думаю, что сложность перевешивает преимущества. Примерно так:
// your reusable MVP framework project
public interface IPresenter<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
V View { get; set; }
}
public interface IView<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
P Presenter { get; }
}
public static class PresenterFactory
{
public static P Presenter<P, V>(this IView<P, V> view)
where P : IPresenter<P, V>, new() where V : IView<P, V>
{
return new P { View = (V)view };
}
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter, IEmployeeView>
{
//...
}
public class EmployeePresenter : IPresenter<EmployeePresenter, IEmployeeView>
{
//...
}
Недостатки
- взаимодействие между формами менее интуитивно для меня.
Шаги:
- агрегат
IEmployeeView
- создание экземпляра презентатора путем вызова
PresenterFactory
и передачи this
из конструктора представления
- убедитесь, что события представления связаны с соответствующими методами презентатора
- начать, как
new EmployeeView()...
.
2) Ведущий запускает вещи и позволяет ему решать их вид. Вы начинаете как, new Presenter().Start();
В этом подходе презентатор создает свое собственное представление (например, подход 1) посредством некоторой инъекции зависимостей или около того, или представление может быть передано в конструктор презентатора. Э.Г.
// your reusable MVP framework project
public abstract class IPresenter<V> // OK may be a better name here
{
protected V View { get; }
protected IPresenter()
{
View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
(View as dynamic).Presenter = this;
}
}
public interface IView<P>
{
P Presenter { get; set; }
}
// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
public void Save()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public EmployeePresenter Presenter { get; set; } // enforced
public void OnSave()
{
Presenter.Save();
}
}
Шаги:
- агрегат
IEmployeeView
- гарантирует, что события представления связаны с соответствующими методами презентатора
- начать, как
new EmployeePresenter(...
.
3) На основе событий, стиль наблюдателя
Здесь вы можете либо инкапсулировать презентатор в представлении (создание экземпляра презентатора в виде), например, подход 1, либо инкапсулировать представление в презентаторе (экземпляр представления в презентаторе), как подход 2, но, по моему опыту, последний всегда будет более чистым проектом для работы. Например, из последних:
// your reusable MVP framework project
public abstract class IPresenter<V> where V : IView
{
protected V View { get; }
protected IPresenter()
{
View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
WireEvents();
}
protected abstract void WireEvents();
}
// your presentation project
public interface IEmployeeView : IView
{
// events helps in observing
event Action OnSave; // for e.g.
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
protected override void WireEvents()
{
View.OnSave += OnSave;
}
void OnSave()
{
var employee = new EmployeeModel
{
Name = View.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public event Action OnSave;
void OnClicked(object sender, EventArgs e) // some event handler
{
OnSave();
}
}
// you kick off like new EmployeePresenter()....
Недостаток:
- Вы должны связать события как на стороне просмотра, так и на стороне докладчика - удвойте работу
Шаги:
- агрегат
IEmployeeView
- гарантирует, что события iview вызываются из методов обработчика событий представления
- обеспечить инициализацию участников события iview от докладчика
- начать, как
new EmployeePresenter()...
.
Ограничения языка иногда усложняют шаблоны проектирования. Например, если бы множественное наследование было возможно в C #, это был только вопрос наличия абстрактного базового класса представления со всеми деталями реализации, кроме специфических компонентов пользовательского интерфейса, которые затем могли быть реализованы классом представления. Нет ведущих, классический полиморфизм и просто мертвецы! К сожалению, это невозможно, поскольку большинство классов представлений в .NET (например, Form
в WinForms) уже наследуются от класса суперпредставлений. Поэтому мы должны реализовать интерфейс и перейти к композиции. Кроме того, C # не позволяет иметь закрытых членов в реализации интерфейса, поэтому мы вынуждены сделать все члены, указанные в IEmployeeView
, общедоступными, что нарушает естественные правила инкапсуляции класса представления (т. Е. Другие представления в проекте представления могут видеть детали EmployeeView
не имеют к ним отношения). В любом случае, используя мощные методы расширения C #, можно использовать гораздо более простой, но очень ограниченный подход.
4) Метод расширения подхода
Это просто глупо.
// your presentation project
public interface IEmployeeView
{
void OnSave(); // some view method
}
public static class EmployeePresenter // OK may need a better name
{
public void Save(this IEmployeeView view)
{
var employee = new EmployeeModel
{
Name = view.Bla // some UI element property on IEmployeedView interface
};
employee.Save();
}
}
// your view project
class EmployeeView : IEmployeeView
{
public void OnSave()
{
this.Save(); // that's it. power of extensions.
}
}
Недостатки:
- довольно непригодно для чего-либо сложного
Шаги:
- агрегат
IEmployeeView
- обеспечить
this....
метод расширения вызывается из событий просмотра
- начать все, позвонив по новому View ...
Из всех 2 и 3 выглядят лучше для меня.