Let’s start with the example I’ve already used, a login form like this:
Your view has two text fields (username, password) and a button (login), as well as a few labels, possibly a title and some explanatory text. We’ll assume that the presenter needs to interact with the text fields and buttons, but that the labels, title and any explanatory text can be encapsulated into the view.
When you build your presenter, you’ll need to decide how the view and the presenter will interact, and there are a few approaches you can take.
Abstracted Control View
Ray Ryan’s Best Practices for Architecting your GWT App uses views that act as containers for controls. Rather than returning the widgets themselves (e.g.Anchor
, Button
, TextField
), the views return interfaces that abstract the specifics of the control from the presenter. This allows the presenter to be completely decoupled from GWT widgets classes, as well as increasing your ability to substitute one control for another.So, for our example login view, we’ll need to expose the username, password and login button, and we can do so through interfaces already supported by these widgets. For instance, the
TextBox
and PasswordTextBox
widgets used for the username and password respectively implement the HasText
interface, which would allow you to retrieve the text that has been entered, and the login button supports HasClickHandlers
, which would allow you to add a ClickHandler
event-handling class to the login button.For our simple case, an abstracted control view would look like this:
This works well for simple cases, but quickly gets complicated. For instance, what if you want to expose an aspect of a control that doesn’t have a convenient interface, like
setVisible()
, which most GWT widgets inherit from UIObject
, but which isn’t part of an interface.One alternative would have you create the interfaces that you need, extend GWT widgets and implement these interfaces. For instance, you could create
HasVisibility
, then create a new class, VisibleAnchor
which extends Anchor
and implements HasVisibility
:Since
setVisible()
comes from UIObject
, each class that inherits from UIObject
has the same consistent method signature, so as long as you’re willing to apply this subclassing approach pervasively, you can create the necessary interfaces.This also gets complicated when you need to expose two or more aspects of one widget. For instance, if your login view chose to change the text on the login button to “logging in …” and disable it while the login process was underway, this requires you to expose three aspects of the login button — enable/disable, setting the text, and adding a click handler.
You can do this by grouping the interfaces you need to expose into interfaces that expose the exact right combination for a given view. You could build interfaces like like
EnableableClickableText
:While this gives you lots of control over the level of abstraction, and gives you a lot of substitutability (
StyledText
could be implemented by TextBox
and Label
while VisibleClickable
could be implemented by Button
and Anchor
), this approach can quickly get out of control. On a project of any size, there are likely to be more combinations here than you care to count let alone implement.Ultimately, if you end up exposing multiple aspects regularly, you’re probably better off accepting that it’s too awkward to maintain every combination of exposed aspects as a separate interface, and simply create an interface that combines all of the aspects you want to expose, either for each class, or for a cluster of related clsses. For instance, you might create a ‘TextInput’ interface that combines all the interfaces that you wish to expose on a
TextField
, and then create a TextInputBox
class that extends TextInput
and TextBox
:As an alternative, you could expose each aspect of a control through an independent accessor. Using the previous example, you could expose the login button control through independent methods that expose the enable/disable, text setting and click handling:
This style is fairly similar to the Interaction View style which follows; most people will find the Interaction View style more appealing.
Interaction View
Where the Abstracted Control View exposes abstractions of controls, the Interaction View exposes the interactions themselves.For instance, although our login view has three controls, it has six known interactions:
- get the username text
- get the password text
- add a click handler for the login button
- set the login button text
- disable the login button
- enable the login button
Each of these can be exposed through a single method:
Alternately, you might choose to group the interactions if they’re likely to be invoked together:
This is closer to the approach used by Daniel Danilatos in GWT Testing Best Practices. If most of your controls expose a large number of unique aspects, this can get tedious — where an abstracted control view might need a single method per control, the interaction view might require several times that. Imagine a data-entry screen with twenty controls and complex behavior to show/hide fields, change styles, display field-level validation, and you'll start to see that this could get pretty messy. In practice, I find that relatively rare, but your mileage may vary depending on your project.
In addition, Interaction View tends to mesh well with mock testing, which is sometimes called Interaction-based testing, in that it allows the test to more clearly expect the intent, rather than needing to mock controls and expect certain messages to be passed to the controls.
View Delegate
Finally, there’s the view delegate style, where the view handles the events, but delegates them to the presenter. This is of particular merit if you’re using GWT UiBinder and its support for@UiHandler
to reduce boilerplate code, and can be seen in Large-Scale Application Development and MVP. In this model, the view will respond to the events of its controls, but delegate the processing of that event to the Presenter.In the case of our
LoginView
, this is something you’d use for the login button click event:This event delegate style fits most naturally as an extension of the Interaction View.
I love your UML diagrams showing the mess with interfaces, but I believe (though I haven't verified) that you actually don't need an "EditableClickableText" interface if you use generics with intersections:
ReplyDelete<T extends HasText & HasClickHandlers & HasEnabled> getLoginButton();
Of course, it doesn't solve the issue where there's no available interface (HasVisibility, HasStyles) or widget doesn't implement the interface you want/need.
The major benefit of the "Interaction View" approach is for mocking in unit tests.
Excellent article! It should be required reading for anyone before they use "MVP" in conversation.
ReplyDeleteI think the subject of testing deserves more emphasis. MVP is nice for splitting things up conceptually, but its real purpose is to achieve fast unit tests for code dealing with UI interactions. How each approach affects those tests would make a great follow-up article.
I'm also curious to know which approaches are used in which MVP frameworks (gwt-mvp, gwt-platform, gwt-presenter, mvp4g, etc.) or if the frameworks are agnostic toward this.
Finally, thank you for not using the word "activity". While GWT's activities and places are useful, they are not an implementation of, nor do they favor any of the above approaches to, the model-view-presenter pattern.
@Brian Reilly:
ReplyDeleteThe main reason I didn't talk about testing and testability in this article, is that I'd written the previous one almost entirely on that subject -- although it might have made sense to talk in the introduction to this article why one might consider MVP.
Most of the MVP frameworks around GWT were just starting out when we started out project, so I haven't dug into them in detail recently. I'll see if I can carve out some time to look at these more closely and see how they relate.
Thanks for your comments.
@Thomas Broyer:
ReplyDeleteI did briefly experiment with the multi-interface generics approach, but I abandoned the experiment, and I don't remember why. I'll have to give that another look-see, see if there's a problem that I've forgotten, or if it's a viable path. It's slightly ugly, but probably less so than interfaces like EditableClickableText. ;)
Can this possibly be tied with the approach postulated at http://code.google.com/webtoolkit/doc/latest/DevGuideMvpActivitiesAndPlaces.html using pure Activities and Places. My understanding is that Activities and Places is the paradigm that is being adapted going ahead by the GWT team.
ReplyDelete@karthik reddy:
ReplyDeleteActivities and Places have actually nothing to do with MVP. They're about navigation within the app. See http://tbroyer.posterous.com/gwt-21-activities (and the previous articles in the series)
Thanks for the pointer about the other article. I should have noticed that.
ReplyDeleteAfter re-watching the first half of GWT Testing Best Practices, I think that what Daniel Danilatos shows there is more like the View Delegate approach, at least in the ThumbnailWidget example. The only difference is that the presenter interface is defined inside the view interface (and is called a "listener"... a convention I prefer over what's shown in "Large scale application development and MVP"). Maybe it's purely philosophical, but I feel like there's a difference between coding to an explicit view interface and coding to the GWT widget and event handler interfaces; Daniel's Thumbnail example is definitely the former, while the "Interaction View" approach shows the latter.
Still, I think this is a great summary of applications of MVP that have been demonstrated for GWT. I also see it as an evolution where the "View Delegate" approach is the latest generation and may remain stable for the foreseeable future.
GWT MVP seem like a hugely over-engineered solution to a simple problem. For each "view" I need at least 4-5 artifacts.
ReplyDeleteMyView
MyViewImpl
MyViewImpl.ui.xml
MyViewPresener
MyViewPresenerImpl
Now, if we add Places and Tokenizers we reach 7 artifacts. Multiply that by a dozen of different views and all the necessary plumbing and I need an army of developers to maintain one app. Compared to that, EJBs pale in complexity! But who cares, we have reached the noble architect goal, maximum level of abstraction and faster unit tests.
Don't worry, someone will develop the next round of XDoclet soon to save us from all these files. ;)
ReplyDelete