Our servlet has become three objects: a controller and two views. (See AlbumServlet4.java for the current state of the sample code. However, I do encourage you not to read the source, and instead to attempt these refactorings on your own.)
One might say that our task is now complete. We have objects representing the Controller (AlbumServlet), the View (AlbumPage and ErrorPage), and the Model (Album). However, there is still one more step we can take.
In some cases, it makes sense for an MVC cluster to have its very own data model, representing all and only the data required by its particular view.
In the present application, we have two separate data layers:
In an abstract sense, the set of parameters passed in to the constructor (the Album and the Request), plus the locally-scoped information like the number of pictures to display per page, represent the Presentation Model for the Album View. For many cases, this level of abstraction will suffice. The View obtains the data it needs directly from the Domain, and/or stores its data inside itself in the form of instance variables.
However, there is value in extracting an explicit Presentation Model object. It makes the code more clearly communicate its intention by making clear exactly what data the view needs in order to accomplish its task.
Also, reducing the set of data elements to only those needed by the view helps clarify that class' dependencies, which in turn prepares you to reduce those dependencies.
Finally, extracting a separate Presentation Model class can improve the unit testing of your application. It is useful to be able to ask the following two questions separately:
Arguably, the current problem is too small to warrant this extra step. But for the sake of argument, let's see where it takes us.
There are a few refactoring tricks to help the extraction procedure. A judicious combination of Encapsulate Field and Inline Method Body keeps the step size small. To move the Album object into the model:
String title = "Album: " + album.getName(); => String title = "Album: " + getAlbum().getName();
class AlbumPageModel
{
private Album album;
private HttpServletRequest request;
public Album getAlbum()
{
return album;
}
public Album getAlbum()
{
return getModel().getAlbum();
}
String title = "Album: " + getModel().getAlbum().getName();
For an alternate path to this same goal, see Introduce Parameter Object (p. 295 of Refactoring).
Four things jumped out at me. (How many did you see?)
Strings of dereferences - - e.g. getModel().getAlbum().getPictures().size() -- are code smells. They signal improper encapsulation. The first step towards resolving this smell is to encapsulate it into a method. The next may be to move this method into the first object in the chain. In this case, the recently-extracted getTotalPictures method belongs in the model (to remove the first indirection). Once a method is in its proper object, you may notice that other indirections disappear as well.
Finally,
4. printPictureTable contains some fairly convoluted logic and looping code. How can we clean it up? Let's separate it into two phases: one which gathers all the pictures to print into a List, and one which prints every item in the list. Along the way, we will make copious use of our old friend, Extract Method.
Now that "calculate all the pictures we need" is reified as the printPictures method, it becomes clear that it needs to move into the View Model as well. You probably saw this coming.
A good general principle of MVC separation is,
The model calculates; the view iterates.In other words, isolate the complexities of figuring out what to render, and let your GUI concentrate on how to render it.
The method printPreviousAndNextLinks is also a candidate for this sort of separation. I leave it as an exercise for the reader.
(AlbumServlet6.java captures our progress so far.)
We now have three classes [3] that more-or-less implement our MVC pattern:
Unfortunately, our job is not yet complete. The classes are still coupled too tightly.
One glaring problem is that the AlbumPage and AlbumPageModel refer to HttpServletRequest. This is an undesirable coupling. It is better to minimize library dependencies. This clears the path for using the object in a situation where we don't have access to a full-fledged Servlet engine - - for instance, inside an application which builds static Web pages, or inside a unit test.
We can resolve this by painstakingly examining each usage of HttpServletRequest, encapsulating it, and extracting it into either the constructor or, cutting to the chase, into the data model. So,
String pageParameter = getRequest().getParameter("page");
becomes
String pageParameter = getPageParameter();where pageParameter is passed in from the outside and stored in a field. Before long, we have completely eliminated a library dependency, at the cost of a few constructor parameters.
public AlbumPageModel(Album album, HttpServletRequest request)becomes
public AlbumPageModel(Album album, String pageParameter)
Another coupling is that the View generates its own model. While this is fine for the simple case, it is very useful to allow (or require) the model to be generated elsewhere and passed in. So the constructor
public AlbumPage(Album album, HttpServletRequest request)
{
this.setModel(new AlbumPageModel(album, request.getParameter("page")));
}
becomes
public AlbumPage(AlbumPageModel model)
{
this.setModel(model);
}
This has the side benefit of removing the dependency on
HttpServletRequest from AlbumPage for free! It also, quite
fortuitously, clarifies the role of the servlet as controller. The
new servlet code (AlbumServlet7.java)
AlbumPageModel model = new AlbumPageModel(album, request.getParameter("page"));
new AlbumPage(model).printPage(out);
clearly communicates the roles, responsibilities, and sequence of
actions. "I, the controller, will create a model; I will then create
a view based on this model, and ask the view to print itself."
In an event-driven GUI context, the controller would have other responsibilities involving event listeners and the like, but the Web, for all its complexities, is actually a fairly simple application model. For instance, there is seldom any notification when the data changes. In a traditional GUI MVC system, a data change would send an event to refresh the view. In a typical Web application, the view remains static, and the user must reload or go to another page in order to see the updated data model.
There is more to do (there always is!) but for now, we have accomplished our stated goal of transforming a somewhat messy servlet into a cluster of objects separating the responsibilities of model, view, and controller. We have opened the door to further improvements in the code; instead of rooting around in a single class, we will be able to home in on the area that needs fixing; and once we get there, the code will be easier to understand (thanks to all our applications of Extract Method and Rename Symbol and the like).
But before we improve the class any further, we should really write a full set of unit tests. It turns out that cleanly separating model from view paves the way for a very interesting testing architecture. But that is a story for another day...
[1] "I've come to the conclusion that people forget about regular Java objects because they haven't got a fancy name - so while preparing for a talk Rebecca Parsons, Josh Mackenzie and I gave them one: POJO (Plain Old Java Object). A POJO domain model is easier to put together, quick to build, can run and test outside of an EJB container, and isn't dependent on EJB (maybe that's why EJB vendors don't encourage you to use them.)"
- Martin Fowler, http://www.martinfowler.com/isa/domainModel.html
[2] or XML, or RMI, or java.io, or EJB, or JNDI, or JMS, or CORBA, or...
[3] We also have ErrorPage (View). The data model for an ErrorPage comprises a single string, the error message. As such, I feel no compelling need to extract it into a separate ErrorPageModel object. However, if you feel that step makes your code clearer, then be my guest.
http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/web-tier/web-tier5.html Web-Tier Application Framework Design J2EE Architecture Approaches: Model-View-Controller Architecture http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/app-arch/app-arch2.html#1106102 Refactoring Catalog http://www.refactoring.com/catalog IntelliJ IDEA http://intellij.com/idea Eclipse http://eclipse.org/ Xrefactory for Emacs http://xref-tech.com/speller/main.html Purple Technology XP Links http://www.purpletech.com/xp/Acknowledgements
Thanks to Chris Lopez, Erik Hanson, and Hilary Nelson providing comments on earlier drafts of this article.