When I create windows application forms, I find it really hard to test it and if I want to change the form design or change the form altogether, I will have to rewrite my code again. So that make my application really rigid and reduce the scalability and testability.
Impact
- Maintenance is really difficult and can't really test my code.
- Its very hard to separate business logic from the view.
Solution
Model view Presenter pattern is the solution to this problem. First we need to understand what Model view Presenter is.
Model: Model is our class where we write our business logic.
View: View is user interface, in our case windows form.
Presenter: Presenter is where we write code to connect our view to our model.
Now the whole purpose of this pattern is to use our view for only the viewing, and all the code should go to presenter class.
suppose we have windows form, called StudentView, and have a model Student, and we are using StudentView to get the information about the student from the user.
this is what our model looks like.
namespace ModelViewPresenterExample
{
public class Student
{
public Student(string name,string address, int age)
{
Name = name;
Address = address;
Age = age;
}
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
}
now that we have our model and view, we need our presenter. we need to create a class name StudentViewPresenter. StudentViewPresenter class implements IStudentViewPresenter interface. We also need to create another interface IStudentView and implement that interface on our Student form. for the time being IStudentViewPresenter has one method and one property.
namespace ModelViewPresenterExample
{
public interface IStudentViewPresenter
{
IStudentView View { get; set; }
void AttachPresenter();
}
}
as you can see we are exposing our form by using IStudentView property. Here is the code for our IStudentView interface.
namespace ModelViewPresenterExample
{
public interface IStudentView
{
void Message(string message);
void AddPresenter(IStudentViewPresenter presenter);
}
}
this interface contains only two methos, one to pass our presenter and one to pass simple messages from our presenter. I know its alot to take in, it will make more sense when we write rest of our code.
so first lets write our presenter class.
namespace ModelViewPresenterExample
{
public class StudentViewPresenter:IStudentViewPresenter
{
private IStudentView _view;
//pass student view when user instantiate presenter.
public StudentViewPresenter(IStudentView view)
{
_view = view;
}
public IStudentView View { get; set; }
//here we are passing this presenter to our view.
public void AttachPresenter()
{
_view.AddPresenter(this);
}
}
}
this purpose of all this is, that using this presenter we could easily switch our view, for example when we create our presenter, we pass the form, presenter does't care what kind of form it is as long as that form implements IStudentView, our presenter will happily accept it.
IStudentViewPresenter presenter = new StudentViewPresenter(new StudentView());
so as you can see, we are forcing the user to inject the view when we instantiate the presenter.
now that we have our presenter, I am going to show you what I have for our form. Here is the code that goes in StudentView.
namespace ModelViewPresenterExample
{
public partial class StudentView : Form,IStudentView
{
private IStudentViewPresenter _presenter;
public StudentView()
{
InitializeComponent();
}
public void Message(string message)
{
MessageBox.Show(message);
}
public void AddPresenter(IStudentViewPresenter presenter)
{
_presenter = presenter;
}
}
}
in student view we have IStudentViewPresenter, we then use AddPresenter method and pass our presenter to this local variable. as you can see that in our presenter class we had a method AttachPresernter and in that method we used our view to call AddPresenter method and passed the presenter to our view, this way presenter can now have connection with view.
now since this form is about saving the student information, when user click on Save button, so we need to write code for that and you will see how our presenter take care of all the code.
private void SaveButton_Click(object sender, EventArgs e)
{
_presenter.Save(NameTextBox.Text, AddressTextBox.Text, AgeTextBox.Text);
}
as you can see we simply created a method named Save and passed all the value to it, infect we just write the code in form and visual studio will create the method for us in the interface, since we are using interface to communicate. we might need to change the names of the parameters though. Now we go to presenter and implement that method.
public void Save(string name, string address, string age)
{
_student = new Student(name,address,Convert.ToInt16(age));
}
as you can see that I am saving all the value to student class, assuming that all the values that were passed in are valid. or we could simply validate the values and then pass it to student class, if the values are not valid then return a message to student view.
public bool Save(string name, string address, string age)
{
if(valid(name,address,age))
{
_student = new Student(name, address, Convert.ToInt16(age));
}
else
{
_view.Message("Invalid values, please enter correct values.");
}
}
as you can see once we have the basic model or MVP, we could add methods or properties to it as per requirements, we could easily test our code as well.
Example of test case using nunit testing.
[Test]
public void ShouldSaveValueToStudent()
{
bool saved = false;
string name = "faraz";
string address = "12Alice road";
string age = "29";
saved = presenter.Save(name,address,age);
Assert.IsTrue(Saved);
}
in this test when we save the data, our method should return true, to conform that every thing went smooth and there was no problem.