Presentation Model Pattern
June 14, 2009
Presentation Model
We want to achieve a Skinny View without the View needing to expose UI details to the Controller.
Also known as
View State, Logical View
Problem
We want to have as much presentation logic under test as we can by having a Skinny View. At the same time we don't want our Controller to be burdened with the minutiae of maintaining UI elements.
Context
We are creating a presentation layer in Flex using some variant of MVC/MVP and want to implement controller logic which modifies the view.
Forces
- Exposing individual UI elements to our controller makes tests fragile and tedious to write.
- Burdening our controller with both abstract presentation logic (what to do when a user clicks a button) and concrete UI display logic (directly modifying the state of UI elements) violates SRP.
- We want to take advantage of the powerful data binding functionality that Flex offers.
- We do not want our Domain/Business logic to be polluted with UI layer details.
Solution
We create a Presentation Model that is a logical representation of the view. The controller maintains the view by manipulating the Presentation Model. We bind our view directly to the Presentation Model, so any changes to the Presentation Model are immediately reflected in the view.
To keep as much logic as possible out of the View and under test we advise not allowing the View to update the model directly. All updating must be done by the Controller. Often that will be initiated via a handler on the Controller called by the view in response to some user action.
Example
In this very simple example we will assume we're working on a Contact Manager application. Specifically we're working on a UI which contains a list of contacts and allows a user to add contacts to the list.
Presentation Model
We'll create a Presentation Model for that screen like this:
package com.example.contacts.ui { import mx.collections.ArrayCollection; [Bindable] public class Model { public function Model() { names = new ArrayCollection(); } public var names:ArrayCollection; } }
View
Next we'll create a view which binds to that Presentation Model:
<?xml version="1.0" encoding="utf-8"?> <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="600" height="600" implements="com.example.contacts.ui.IView" creationComplete="onCreationComplete()"> <mx:HBox> <mx:VBox> <mx:Form height="100%" width="100%"> <mx:FormItem label="First Name:"> <mx:TextInput id="tiFirstName"/> </mx:FormItem> <mx:FormItem label="Last Name:"> <mx:TextInput id="tiLastName"/> </mx:FormItem> <mx:Button label="Add Contact" click="onAddContactClick()"/> </mx:Form> </mx:VBox> <mx:VBox> <mx:List id="cbNames" dataProvider="{_model.names}" /> </mx:VBox> </mx:HBox> <mx:Script> <![CDATA[ import com.example.contacts.*; private var _controller:Controller = null; [Bindable] private var _model:Model = null; public function set model( value :Model ):void { _model = value; } private function onAddContactClick():void { _controller.onAddName( tiFirstName.text, tiLastName.text ); } public function clearNameInputFields():void { tiFirstName.text = ''; tiLastName.text = ''; } ]]> </mx:Script> </mx:Canvas>
Controller
Finally we'll implement our Controller:
package com.example.contacts.ui { public class Controller { private var _model: Model; private var _view : IView; public function Controller() { _model = new Model(); _view = null; } internal function bindView(view: IView): void { _view = view; _view.model = _model; } internal function onAddName( firstName:String, lastName:String ):void { _model.names.addItem( lastName + ", " + firstName ); } } }
Communication Diagram
The communication diagram above shows how the three classes collaborate to handle a user interaction event. The user takes an action, which results in an event handler in the View being called. The View's event handler delegates immediately to a handler method in the Controller. The Controller's handler method performs whatever logic is necessary, including updating the Presentation Model. That update indirectly triggers Flex data binding to update the View's UI elements. Note that communication tends to travel in one direction here - From View out to Controller, from Controller to Presentation Model, and from Presentation Model back to View. It is rare for a Controller to communicate directly with its View, and it is expressly forbidden for a View to directly manipulate the Presentation Model.
Note that the View is very skinny - it mostly just binds to the model and delegates handlers straight through to the Controller. Also note that the controller doesn't interact directly with the View when manipulating the UI. It just manipulates the Presentation Model, which results in the UI updating thanks to the View's data binding.
You can also see that we didn't follow the Pattern dogmatically in this example. We could have pulled the View's clearNameInputFields() method into the Controller by representing the contents of those 2 input fields in the Presentation Model and then binding those to the view. That would then allow us to clear the input fields by clearing the corresponding Presentation Model properties. However, in this case I decided that it was a nicer separation of concerns to hide the details of those elements from the Controller and the Presentation Model. Instead the View exposes a simple method with an Intention Revealing Name. This removes the need for the Presentation Model to be cluttered with these elements just to serve the one operation of clearing the fields.
This is a very basic example, but hopefully it conveys the concept. For a more sophisticated UI the View would have additional UI elements which would be bound to additional properties added into the Presentation Model.
Resulting Context
- It is extremely easy to test how our Controller is modifying our View.
- Our View no longer has to expose its UI elements.
- Our Controller no longer has to deal with the View's UI elements.
- Data binding allows our View to contain very little logic related to updating UI elements.
Rationale
It is really powerful to take advantage of data binding in Flex in order to simplify your view logic. At the same time it doesn't make sense to bind directly to our domain model classes. These classes are focused on representing the relationships and rules of our business domain, and should not be concerned with the UI layer at all. We need to map domain concepts like 'Collection Of Users' to UI concepts like 'list of strings for a dropdown'. Presentation Model allows us to do that. Presentation Model also allows us to very easily test the way our Controller updates the UI. Our tests simply have to exercise the Controller logic that is under test and then check the state of the Presentation Model.
Related Patterns
Presentation Model is really just a twist on Fowler's Two Step View, focusing on MVC/MVP and with a consideration of Flex data binding.
Authors
Pete Hodgson, Hosung Hwang