The main reason for separating view and logic is unit testing.
There are many patterns for separating view and logic (MVC, MVP, MVVM or even MVP-VM). Comparison is not subject of the article and i just note main advantages of MVP:
- MVP is very simple pattern.
- MVP allows to test more than other patterns and tests are more clear.
Classical MVP is too abstract pattern. Real implementation generally has more than 3 participants. This article is practical guide for using and testing of MVP.
- Presenter communicates with view directly
- Presenter communicates with view within Model (using binding)
- Inside view
// Constructor
public View()
{
var presenter = new Presenter(this);
}
// view creation
var view = new View();
view.Show();
- Outside View (for multiple Presenter implementations)
var view = new View();
var presenter = new Presenter(view);
view.Show();
Presenter(IView view)
{
this.view = view;
this.model = new Model();
view.FormLoadEvent += this.Load;
view.ButtonClickEvent += this.Click;
}
- Simplifying firing:
form.Load += (s, args) =>{ FormLoadEvent(); };
button.Click += (s, args) =>{ ButtonClickEvent(args); };
- Using Commands: todo
void view_FieldValidating(CancelEventHandler e)
{
e.Cancel = /* expression */;
}
void Presenter.DoSomething()
{
view.BindModel(model);
view.AskUser();
view.MakeBeep();
}
- Class diagram
- Binding:
var presenter = new Presenter(view, new Service());
To hide internal implementation of component inside assembly declare interface as "internal" and implement it explicitly in view (do it if you really need it).
internal interface IView
{
void f();
event Action<string> act;
}
public class View : IView
{
private object objectLock = new Object();
internal event Action<string> act;
void IView.f() { }
event Action<string> IView.act
{
add { lock (objectLock) { act += value; } }
remove { lock (objectLock) { act -= value; } }
}
}
- Raising events:
var viewMock = new Mock<IView>();
var presenter = new Presenter(viewMock.Object);
viewMock.Raise (m =>m.LoadEvent += null, EventArgs.Empty);
Assert.AreEqual( … );
- Check pulling/calling of view/service methods:
/* view methods invocation verification */
viewMock.Verify(v =>v.MakeBeep(), Times.Once());
viewMock.Verify(v =>v.AskUser(), Times.Once());
/* service methods invocation verification */
viewMock.Verify(s =>s.SaveData(), Times.Once());
- Use Friend Assemblies to test “internal” members (attribute InternalsVisibleTo). See details on https://msdn.microsoft.com/en-us/library/0tke9fxk.aspx
- Extending logic of Presenter (composition/aggregation is preferable from Unit Testing perspective).
var view =new View();
var presenter1 = new Presenter1(view);
var presenter2 = new Presenter2(view, presenter1);
view.Show();
- Interaction of several views.
- Class diagram:
- Main view interacts with child within public contract of child view.
MainView.SomeMethod()
{
view1.Refresh();
view2.Refresh();
}
- Child view interacts with main within itself public events
public MainView()
{
// event subscription
view1.DataUpdated += this.DataUpdatedHandler;
}
View1.SomeMethod()
{
// firing event
DataUpdated();
}