Our four friends are crafting a strategy for the inevitable and highly anticipated clash with the minions of the Lady de Winter. Mighty Porthos has just finished a long oration about his Passive View approach to creating maintainable WinForms screens. The crafty Aramis has started his own oratory on his preferred approach to avoid so much Interaction Based Testing by utilizing...
The Presentation Model
The Presentation Model differs from the two Model View Presenter approaches (Supervising Controller and Passive View) by combining the "M" and the "P" into a single class. This single Presentation Model class both contains the state of the screen and implements the behavior of the screen. Compared to the Supervising Controller, Presentation Model is more complex in that it also implements the state of the screen, but also less complex because the state synchronization is almost entirely the responsibility of the View itself. Instead of a Presenter directing the View to change the state of the screen, the Presentation Model simply changes its own state and depends on Data Binding (or an equivalent) to update the screen accordingly.
Let's jump right into our third and final implementation of the Shipping Screen. As I stated before, the View will use data binding to bind its screen elements to the public properties on the new ScreenPresentationModel class.
public partial class ShippingScreen : Form
{
public ShippingScreen()
{
InitializeComponent();
}
public void Bind(ShippingPresentationModel model)
{
shipmentBindingSource.DataSource = model;
}
}
I'll spare you the rest of the data binding setup code, and besides, that's covered in other literature to a vastly greater degree than Presentation Model. Besides which, I've barely worked with data binding and you've probably guessed correctly that I'm more than a little biased against it.
So far we haven't seen anything that different from Supervising Controller, but when you use the Presentation Model approach you're probably exploiting the fact that data binding in WinForms can also bind to the "Visible" and "Enabled" properties of controls and not just the value. In the case of the Shipping screen, we'll bind the "Enabled" properties of the checkbox's for selecting insurance and requiring a signature to properties on the ShippingPresentationModel shown below.
public bool InsuranceEnabled
{
get { return _insuranceEnabled; }
set { _insuranceEnabled = value; }
}
public bool SignatureEnabled
{
get { return _signatureEnabled; }
set { _signatureEnabled = value; }
}
public string Vendor
{
get { return _vendor; }
set
{
_vendor = value;
fireChanged("Vendor");
DeliveryOptions options = _service.GetDeliveryOptions(this);
InsuranceEnabled = options.PurchaseInsuranceEnabled;
SignatureEnabled = options.RequireSignatureEnabled;
}
}
In the above code, anytime a user selects a different shipping vendor the data binding will call the setter for Vendor on ShippingPresentationModel, causing a recalculation of the InsuranceEnabled and SignatureEnabled properties, which finally causes the two checkbox's to be either enabled or disabled depending upon the shipping vendor selected. All because a little bug went kachooo!
The communication between View and the Presentation Model is relatively simple, the View simply sets properties on the PresentationModel class and cascading actions are triggered in the setters. I've purposely put off talking about View to Presenter communication, but let's just say that this aspect of the Presentation Model is simpler than either Supervising Controller or Passive View.
One last example of the ShippingPresentationModel. There are three or four factors that influence the cost of the shipment. If any of these screen elements change, the cost needs to reevaluated. With Presentation Model, I just capture all change events inside the setters, so the trigger to reevaluate the shipping cost is something like this:
public string ShippingOption
{
get { return _shippingOption; }
set
{
_shippingOption = value;
fireChanged("ShippingOption");
_service.CalculateCost(this);
}
}
public bool PurchaseInsurance
{
get { return _purchaseInsurance; }
set
{
_purchaseInsurance = value;
fireChanged("PurchaseInsurance");
_service.CalculateCost(this);
}
}
public bool RequireSignature
{
get { return _requireSignature; }
set
{
_requireSignature = value;
fireChanged("RequireSignature");
_service.CalculateCost(this);
}
}
I simply make a call to IShippingService.CalculateCost(IShipment) anytime a property changes that impacts the shipping calculation. For convenience sake, I made ShippingPresentationModel implement a common IShipment interface that is consumed by IShippingService, if you're wondering where in the world the "this" parameter was coming from. I'm assuming that the IShippingService will itself set the IShipment.Cost property. The signatures for IShippingService still looks like this:
public interface IShippingService
{
string[] GetLocations();
string[] GetShippingVendorsForLocation(string location);
string[] GetShippingOptions(IShipment shipment);
void CalculateCost(IShipment shipment);
DeliveryOptions GetDeliveryOptions(IShipment shipment);
}
State Based Unit Testing
The biggest difference to me in using Presentation Model versus one of the MVP patterns is the shift to state based testing inside of our xUnit tests. I'm more or less a "mockist" I guess, but I've worked with more than a few people who've had almost allergic reactions to using mock objects. If you're one of those people, don't worry, you're not out of luck because you can do more or less state based testing with Presentation Model. Here's an example of what I mean (even though out of pure contrariness I'm using RhinoMocks to create my stub):
[Test]
public void ResetTheShippingOptionsWhenTheStateOrProvinceIsChanged()
{
// We're going to test ShippingPresentationModel in a state-based manner
// I'm using RhinoMocks to create the stub just because
// a.) It's easy
// b.) I hate cluttering up the code with static mocks and stubs if
// I don't have to.
// You might note that I'm not even bothering to call mocks.VerifyAll()
MockRepository mocks = new MockRepository();
IShippingService service = mocks.CreateMock<IShippingService>();
ShippingPresentationModel model = new ShippingPresentationModel(service);
string[] theOptions = new string[]{"Option 1", "Option 2", "Option 3"};
Expect.Call(service.GetDeliveryOptions(model)).Return(theOptions).Repeat.Any();
mocks.ReplayAll();
// We need to verify that the model starts with a zero array string
Assert.AreEqual(0, model.ShippingOptions.Length);
// I'm not sure I'd bother testing the raising of the PropertyChanged event,
// or at least do it in another test.
bool propertyWasCalled = false;
model.PropertyChanged += delegate (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
propertyWasCalled = e.PropertyName == "ShippingOptions";
};
model.StateOrProvince = "TX";
// Check that the state of the model changed
Assert.AreEqual(theOptions, model.ShippingOptions);
// And while we're at it, let's check that the PropertyNotified event was called
Assert.IsTrue(propertyWasCalled);
}
All I'm testing here is that the ShippingPresentationModel gets a list of Shipping Options whenever the StateOrProvince property is changed, then resets its ShippingOptions property. I'm not real wild about it, but I also showed using an anonymous delegate to check that the PropertyChanged event was fired for "ShippingOptions." To recap, the expected sequence of events is:
- The user selects a value in the State or Province select box.
- Data binding in the view sets the StateOrProvince property on ShippingPresentationModel. Since the View is just talking directly to getters and setters, we really don't need the View involved in this unit test at all.
- In the setter for StateOrProvince, the ShippingPresentationModel should find the ShippingOptions for the selected state or province and set it's internal value for ShippingOptions which...
- Fires the PropertyChanged event for "ShippingOptions" which directs the magical data binding support to fill the dropdown list for Shipping Options with new values (which I didn't show because it's documented very well on MSDN).
Whew. The code that implements this test above is much simpler:
public string StateOrProvince
{
get { return _stateOrProvince; }
set
{
_stateOrProvince = value;
// Whenever this property changes, we need to reset the
// ShippingOptions to match the StateOrProvince
ShippingOptions = _service.GetShippingOptions(this);
fireChanged("StateOrProvince");
}
}
public string[] ShippingOptions
{
get { return _shippingOptions; }
set
{
_shippingOptions = value;
fireChanged("ShippingOptions");
}
}
Summary
The Presentation Model is another example of a Humble View. It largely differs from the Model View Presenter patterns by combining the Model and Presenter into a single class. It's important to note that the Presentation Model most likely encapsulates the actual application model, and it's definitely part of the user interface rather than a domain model class implementing pure business logic. While it does a great job isolating behavior from the View and exposing the behavior in a way that allows for state based testing, you might find yourself getting annoyed at all the delegation that has to take place between the Presentation Model class and the inner application model. Then again, a buffer between the user interface and the rest of the application might just be a good thing.
Honestly, I haven't used this pattern but a time or two. The largest implementation I've seen was actually a Java Swing client where it was used quite effectively.
I do have an example from StoryTeller that I will probably use in the posts on creating an Application Shell where I use Presentation Model as a kind of state machine to synchronize menu state as the screen mode changes. I'm leaving it out now for the sake of brevity (and my impending bedtime).
Other Resources
- I think Presentation Model is another name for the Model/View/ViewModel pattern being promoted by some of the WPF team at Microsoft. I still think I'd rather stick with Supervising Controller in most cases, but I'm thrilled that people in MS itself are talking about this at all.
- It's an old post, but I'd read Michael Swanson's thoughts on Presentation Model before you run off and use the pattern.
Three Musketeers Retirement Notice
The silly Three Musketeers thematic interludes just require more creativity than I can summon on a regular basis. Let's just say they stopped the evil Lady de Winter in her diabolical mission (even though she's usually the most interesting character in the movie adaptations. Seriously, who are you going to root for, Chris O'Donnell or Rebecca De Mornay/Faye Dunnaway? That's what I thought;) and delivered their very complex WinForms application on time with minimal fuss with a healthy infrastructure of automated testing. And for the hard core Dumas fans, let's just forget about how badly things end in the Man in the Iron Mask because it still depresses me in a way that has only been topped by "they killed Wash!."
Where Next?
I've got to tally up the poll on this one, but for the sake of narrative continuity I'm going to wrap up Model View Presenter with some quick and easy to write posts on View/Presenter communication and dividing roles. After that, I'm not sure yet, but the response has been so positive that I'll definitely keep going for a while. I will finish this by no later than mid July. I'm shooting for 2-3 posts a week.