In an attempt to orient myself and my colleagues with GWT, I've been reading up on GWT, trying things out, and blogging about them here. My goal is to start with simple use cases and build on to them with more complex ones. The way it's been working so far is that each post builds on the prior one.

Tuesday, February 16, 2010

Why should the View know nothing about the Model?

 

An advantage of coding in Java is that our code can be strongly-typed. Since our View represents the UI, perhaps we’d design the ContactListView to have a method to addContactToList where we pass it a strongly-typed element of the Model, Contact, and the ContactListView would, in turn, add an HTML anchor as a list item to the Contact list.


public interface ContactListView{
public void addContactToList(Contact contact);
}


But we said above that our View really shouldn’t know anything about the Model and passing Contact in to the View contradicts that. In a GWT world of strongly-typed things, why is this so? Why not pass Model objects to the View and return Model objects from the View when events are raised?



It’s mostly about testability. If we pass Model objects to the View and the UI is loosely typed, then that means the View is responsible for doing translation from the Model to the UI and vice-versa. “Translation” means logic and logic necessitates unit testing. UI is traditionally hard to test and GWT is no exception. (It’s a bit easier with GWT than other approaches, but it’s still much slower than being able to test independent of a UI.) Since UI is hard to test and the View ultimately represents the UI, the View is hard to test, also. So to maximize testability, we will give the responsibility of Model-to-UI translation to the Presenter. Although not the primary driver, there is also the possibility that leaving the Model out the View will improve reusability, the ability to use a particular View with different Models.



To ensure that our View doesn’t have to do a Model-to-UI translation, we could change our View to instead add a Contact to its contact list with a String instead of a Contact:



public interface ContactListView{
public void addContactToList(String contactFirstName);
}


Now the View->to->UI translation is trivial since we passed it data that it can trivially translate to HTML. And since it’s trivial, it’s not the end of the world if we don’t frequently regression test this aspect of the View. There’s not too much that can go wrong here. The Presenter is responsible for the binding/translation of the Model to the View and can have code like so:




public void setContactList(Collection contacts){
for (Contact contact : contacts){
contactListView.addContact(contact.getFirstName());
}
}


We can now test this translation by mocking the View and we could catch an error if, for example, we bind last name instead of first name from the Model to the View/UI.



This seems fine for sending data to the View, but how should we model events that are raised from the UI? We ultimately want to tie those events back to the Model. Going back to our example, when a user clicks on ‘Joe’, the HTML link, we ultimately want an event raised that ‘Joe’, the Contact, was clicked so that we can pass it, strongly-typed, to the ContactDetailPresenter. Since we passed a primitive String to the View, the View doesn’t know that a click on ‘Joe’ represents Joe the Contact. Remember one characteristic of our UI is to raise events and call out to do stuff. Since our UI can do this, it is reasonable that our View can do this too. In a GWT MVP world, we could accomplish this by returning a HasClickHandlers interface from the addContactToList() method. With this interface, the Presenter can add handlers for click events.




public interface ContactListView{
public HasClickHandlers addContactToList(String contactFirstName);
}


Note: HasClickHandlers is an interface delivered in the core GWT library.

Now when the Presenter does a Model->View translation, it can also add a listener/handler to click-events in a strongly-typed way. Here is some code we could now write in the ContactListPresenter. Notice that a click-handler is added for each Contact added to the view.




public void setContactList(Collection contacts){
for (final Contact contact : contacts){
HasClickHandlers hasClickHandlers = contactListView.addContact(contact.getName());

hasClickHandlers.addClickHandler(new ClickHandler() {

public void onClick(ClickEvent event) {
// do something with the strongly-typed Contact

}
});

}
}


Now our ContactListPresenter has done a full Model -> UI translation for rendering and a UI -> Model translation on event-handling. The View has no logic in it specific to the Model. (In fact, we haven’t even actually implemented our View yet.) All the View needs to do is accept UI-primitive data and raise UI-primitive events to those listening for them. And back to the testability concerns, we can unit test all the Model <-> UI translation without testing the View. This is a good thing since, as we discussed before, the View is more challenging to test.



Similarly for the ContactDetailView, it could have a View that looks like the following to the ContactDetailPresenter can listen for changes the user makes to the Contact’s first name:




public interface ContactDetailView {
public HasChangeHandlers setFirstName(String firstName);
}

Note: HasChangeHandlers is an interface delivered in the core GWT library.

And a ContactDetailPresenter could interact with it like so:




public void setContact(final Contact contact){
HasChangeHandlers changeHandlers = view.setFirstName(contact.getName());
changeHandlers.addChangeHandler(new ChangeHandler() {

public void onChange(ChangeEvent event) {
// do something with the strongly-typed Contact
}
});
}

No comments:

Post a Comment

Followers