-
Notifications
You must be signed in to change notification settings - Fork 7
Basic Principle
The Model-View-Presenter (MVP) pattern is more of a design principle than a traditional programming pattern (like Observer or Visitor). This page will attempt to explain the principles of the MVP-approach used by MVP4Vaadin.
Let's start with a simple UML-diagram:
The basic idea behind MVP is to separate the UI implementation from the UI logic. This has several advantages:
- Possible to write unit tests for the UI logic
- Possible to re-use the UI logic in different UI implementations
- Clearer design as the developer has to think through what user interactions take place in a single view
We are now going to take a closer look at the different parts of MVP.
The presenter contains the UI logic. When the user performs an action that does not affect the UI only (i.e. performs a search or saves an entity), the presenter is invoked, performs the action and updates the view. The presenter always accesses its view through a interface and is completely unaware of how the view is actually implemented.
Information can be passed from the view to the presenter in two different ways. Both are good, but for different usage scenarios. You should pick the one that best fits your particular situation.
The first way is the simplest one, in which the view maintains its own state (e.g. a form) and transfers it to the presenter as a method call parameter. For example:
// The method is defined in the View
private void saveButtonClicked() {
Person personToSave = new Person();
personToSave.setFirstName(firstNameTextField.getValue());
personToSave.setLastName(lastNameTextField.getValue());
getPresenter().savePerson(personToSave);
}
The advantage of this approach is that the code for both the presenter and the view becomes quite simple. State is not transferred between the view and the presenter until it is needed and the view is free to decide how to handle the data before it is passed to the presenter.
The second way is to keep the state of the view and the state of the presenter synchronized at all times. For example:
// The methods are defined in the view
private void onFirstNameChanged(String newFirstName) {
getPresenter().setFirstName(newFirstName);
}
private void onLastNameChanged(String newLastName) {
getPresenter().setLastName(newLastName);
}
private void saveButtonClicked() {
getPresenter().savePerson();
}
This approach requires more code and also increases the coupling between the view and the presenter. On the other hand it makes it possible to the presenter to react on data changes as they are made. This could be useful if there are several views using the same data model and a change in one view should be reflected in the other views immediately.
The view is responsible for showing data to and collecting data from the user. The view implements a view interface that is used by the presenter to instruct the view. The interface could look something like this:
public interface MyViewInterface {
void clearForm();
void showSaveSuccessMessage();
void showSaveFailureMessage();
void populateForm(MyData data);
}
As the purpose of MVP is to separate the UI logic from the UI implementation, the view interface should not contain classes that ties it to a specific UI technology (such as Vaadin, Swing or SWT). The presenter instructs the view what to do, but the view is free to decide how to do it.
So how much logic can a view implement on its own and when does it have to invoke the presenter? Well, it depends. A good rule of thumb is that the presenter need only be invoked when the view boundary is crossed. This is best illustrated with three examples:
- When a text field loses focus, a label should be updated with the contents of the text field. This operation does not cross the view boundary and can be implemented by the view directly.
- When a text field loses focus, a database search should be performed using the contents of the text field as a search term and the result presented in a table. This operation crosses the view boundary and has to be implemented by the presenter.
- When a text field loses focus, a shared model should be updated so that other views sharing the same model can update themselves. This operation crosses the view boundary and has to be implemented by the presenter.
The data of the application live in the model. How the model is implemented depends on the data and whether the model is shared by multiple views. A model can range from being a simple field in the presenter class to being a database or file.
If the model is a database, you normally invoke it through some kind of data access object (DAO). In this case, no separate model class is required and the presenter can invoke the DAO directly.
If the model is shared by many views simultaneously and a change in the model should be reflected in all views, you have to create a separate model class that fires events when it changes. The presenters then register themselves with the model and update their views accordingly.
When you build your user interface with MVP, you have to remember that you are working with views, not presenters. The view is responsible for creating, invoking and destroying the presenter and normally no outside classes should even need to be aware of the presenter's existence. The view logic has simply been refactored into a separate class instead of being cluttered throughout the view class itself.
There are variations to the Model-View-Controller (MVC) pattern as well, but generally, the principle is this:
- The model contains the data of the application.
- The view contains the UI widgets.
- The controller listens to both the model and the view and moves data between them.
In other words, the controller contains all of the logic, including click listeners and text change listeners and what-not. The presenter, on the other hand, does not care about this. The view registers its own listeners and only invokes the presenter on certain occasions (as has been discussed earlier).
Although it is theoretically possible to create a presenter that is usable with many different kinds of view implementations, there are a few things to keep in mind. This is best illustrated with an example.
Modal dialogs are often used in rich application user interfaces. However, they are implemented in different ways in, say, Swing and Vaadin.
In Swing, when you show a modal dialog, the current thread blocks until the dialog is closed and control is transferred to the dialog. Thus, you could write code that looks like this:
public interface MyView {
boolean confirmDelete();
}
public class MyPresenter {
public void delete() {
if (myView.confirmDelete()) {
myService.delete();
}
}
}
This will work fine if the view is implemented in Swing. The confirmDelete()
method shows a modal yes/no dialog, blocking the current thread until the user clicks on any of the buttons. Then control returns to the presenter who proceeds with calling myService
if the user clicked yes.
However, if you try to implement the same view in Vaadin you will run into trouble. As Vaadin is a web-based application, every operation is carried out as a stateless HTTP request within the same thread. Therefore, you can not show a modal dialog and block the thread until the dialog is closed. This is a limitation that you have to take into account when designing your view and presenter. The following approach will work both with Vaadin and with Swing:
public interface MyView {
void showDeleteConfirmation();
}
public class MyPresenter {
public void delete() {
myView.showDeleteConfirmation();
}
public void deleteConfirmed() {
myService.delete();
}
}
In this case, when the user first clicks the delete button, the presenter only instructs the view to ask for confirmation. The view will then show a confirmation dialog and wait for the user to select an option. If the user clicks the Yes button, the view invokes the presenter again.