Supervising Controller
Factor the UI into a view and controller where the view handles simple mapping to the underlying model and the the controller handles input response and complex view logic.
19 June 2006
This is part of the Further Enterprise Application Architecture development writing that I was doing in the mid 2000’s. Sadly too many other things have claimed my attention since, so I haven’t had time to work on them further, nor do I see much time in the foreseeable future. As such this material is very much in draft form and I won’t be doing any corrections or updates until I’m able to find time to work on it again.
Many UI frameworks provide the ability to easily map between the view and model, often using some kind of Data Binding. These approaches are very effective in allowing you to declaratively set up a relationship between elements in the view and model. Usually, however, there are more complex relationships which require you to have more complex view logic. This logic can be hard to manage, and in particular hard to test, while embedded in the view.
Supervising Controller uses a controller both to handle input response but also to manipulate the view to handle more complex view logic. It leaves simple view behavior to the declarative system, intervening only when effects are needed that are beyond what can be achieved declaratively.
How it Works
Supervising Controller decomposes presentation functionality into two parts: a controller (often called presenter) and view. The domain data that needs to be displayed is separate, and following rough MVC terminology I'll refer to it as a model, although it need not be a Domain Model. The basic division of responsibilities echoes the Model-View-Presenter architecture in its Dolphin form, as described by Bower and McGlashan.
A Supervising Controller has two primary responsibilities: input response and partial view/model synchronization.
For input response the controller operates in the presenter style. The user gestures are handled initially by the screen widgets, however all they do in response is to hand these events off to the presenter, which handles all further logic.
For view/model synchronization the controller defers as much of this as reasonable to the view. The view typically uses some form of Data Binding to populate much of the information for its fields. Where Data Binding isn't up to more complex interactions then the controller steps in.
Figure 1: Class diagram for the assessment example.
Figure 2: Sequence diagram showing the response for putting in a low actual value.
In the assessment window, the initial text change is handled by
the text field widget in the view. This widget is observed by the
controller, so when the text changes the widgets emits an event
which results in the controller's actualFieldChanged
method being
called. This method then handles the full response to the
event. It first updates the reading model object's actual
value. The window observes the reading object so the change to the
reading's value triggers a refresh. It's pretty easy to map the
actual and variance text fields' text to the appropriate
properties of the reading, so that updates those values. For the
purposes of our example changing the color is a bit more
involved. The reading object can, and should, determine which
category the reading should fit into. A sophisticated widget might
be able to bind its text color to a category like this, but we'll
assume we don't have such a clever young thing. In this case
controller takes over setting the variance field's text color directly.
As the example shows, the essence of a good Supervising Controller is to do as little as possible. Let the view handle as much as possible and only step in when there's more complex logic involved.
One of the prime reasons to use Supervising Controller is for testability. Assuming the view is hard to test, by moving any complex logic into the controller, we put the logic in a place that's easier to test. In order to run tests on the controller, however, we do need some form of view, so a Test Double is often in order. With the double in place, we don't have any need for UI framework objects in order to test the more awkward parts of the UI behavior.
This testability issue affects another decision - whether the controller should access the view and its widgets directly, or through an intermediary. With an intermediary we build a Gateway for the the controller. The gateway defines an interface for the controller to manipulate. One implementation then adapts the window's interface while the other provides a stub for testing (you could also use a mock). This is the same approach that you need for Passive View.
Figure 3: Using an intermediary between the controller and the window.
The discussion so far suggests using Flow Synchronization with Supervising Controller, but this need not be the case. It is possible to use Observer Synchronization, but it needs to be modified so that it's the controllers that observe the model rather than the views.
When to Use It
There are two main reasons to look into using a Supervising Controller: to separate out the complexity of an Autonomous View, and to improve testability.
The separation advantage is that it pulls all the behavioral complexity away from the basic window itself, making it easier to understand. This advantage is offset by the fact that the controller is still closely coupled to its screen, needing a pretty intimate knowledge of the details of the screen. In which case there is a real question mark over whether it's worth the effort of making it a separate object.
The testability reason is the more compelling one. Many people I've talked to have found using some form of controller has made a big difference to making a UI that is properly testable.
If testability is your driver then the driving issue is how much behavior to leave in the view. Passive View is a very similar pattern to Supervising Controller, but with the difference that Passive View puts all the view update behavior in the controller, including simple cases. This results in extra programming, but does mean that all the presentation behavior is testable. The choice between the two depends on what kind of Data Binding support you have and whether you're happy to leave that untested by controller tests.
A third alternative is Presentation Model. This again pulls most behavior away from the view, but leaves it to the view to synchronize all its updates. There isn't a great deal of the difference in test coverage between Presentation Model and Supervising Controller - much of the choice (as with Passive View) depends on personal judgments.