In the Passive View Model View Presenter pattern, who has the responsibility for displaying the view? I have found related answers for other MVP versions, but they don't seem applicable to the passive view version.
I have a concrete example using Java Swing. It's pretty simple, but basically we have a SwingCustomersView
which internally builds a JPanel with a table (list of customers) and a label displaying the currently selected customers age. When a customer is selected in the table, the presenter retrieves the selected customer age from the model. I think the example is a correct implementation of MVP Passive View, but correct me if I'm wrong.
The question is how do we bootstrap these classes? For example, if we wanted to display the SwingCustomersView
in a JFrame. How would one do that? I imagine something along the lines of:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
}
This is the initial wiring, but nothing is displayed yet. How do we actually display the view? Is it the responsibility of (1) launcher()
, (2) SwingCustomersView
or (3) CustomersPresenter
to display the view? Unfortunately I don't believe any of those are very good as you can see from my thoughts below. Perhaps there's another way?
(1.a): launcher
Make SwingCustomersView
extend JFrame and make it add it's internal JPanel to the content pane of itself. Then we can do this:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
view.setVisible(true); // Displays the view
}
However in this case we don't use the presenter
instance for anything. Isn't that strange? It's just there for wiring, we could just as well delete the variable and just do new CustomersPresenter(view, model)
.
(2): SwingCustomersView
Make SwingCustomersView
take a Container
in the constructor to which it should add it's internal JPanel:
void launcher() {
CustomersModel model = new CustomersModel();
JFrame frame = new JFrame("Some title");
SwingCustomersView view = new SwingCustomersView(frame.getContentPane());
CustomersPresenter presenter = new CustomersPresenter(view, model);
frame.pack();
frame.setVisible(true) // Displays the view
}
However, same problem as (1): the presenter
instance does nothing. It seems strange. Furthermore with both (1) and (2) it is possible to display the view before the presenter is hooked up, which I imagine could cause strange results in some situations.
(3): CustomersPresenter
Make CustomersPresenter
responsible for displaying the view somwhow. Then we could do this:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
presenter.show() // Displays the view
}
This would solve the problem of not using it for anything after construction. But I don't see how do to this without either changing the CustomersView
interface or making CustomersPresenter
too dependent on the underlying GUI implementation. Furthermore, displaying a view doesn't sound like presentation logic and thus doesn't seem to belong in the presenter.
Example
public class CustomersModel {
private List<Customer> customers;
public CustomersModel() {
customers = new ArrayList<Customer>();
customers.add(new Customer("SomeCustomer", "31"));
customers.add(new Customer("SomeCustomer", "32"));
}
public List<Customer> getCustomers() {
return customers;
}
}
public class Customer {
public String name;
public String age;
public Customer(String name, String age) {
this.name = name;
this.age = age;
}
}
public interface CustomersView {
void addCustomerSelectionChangeListener(ItemListener listener);
void onNewActiveCustomer(String age);
void onNewCustomers(List<String> newCustomers);
}
public class SwingCustomersView implements CustomersView {
// Swing components here all put into a main JPanel
public void addCustomerSelectionChangeListener(ItemListener listener) {
// Add event listener to table
}
public void onNewActiveCustomer(String age) {
// Display age in label beneath table
}
public void onNewCustomers(List<String> newCustomers) {
// Display customers in table
}
}
public class CustomersPresenter {
private final CustomersView view;
private final CustomersModel model;
public CustomersPresenter(CustomersView view, CustomersModel model) {
this.view = view;
this.model = model;
initPresentationLogic();
populateView();
}
private void initPresentationLogic() {
view.addCustomerSelectionChangeListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
String selectedName = (String)e.getItem();
List<Customer> customers = model.getCustomers();
for (Customer c : customers)
if (c.name.equals(selectedName))
view.onNewActiveCustomer(c.age);
}
});
}
private void populateView() {
List<Customer> customers = model.getCustomers();
List<String> names = new ArrayList<String>();
for (Customer c : customers)
names.add(c.name);
// View will now populate its table, which in turn will call customerSelectionChangeListener
// so the view 'automagically' updates the selected contact age too
view.onNewCustomers(names);
}
}
See Question&Answers more detail:os