When last we left our brave companions, between courses of cheese and fine wine, Athos was sharing his strategy for dividing screen responsibilities by employing the Supervising Controller pattern. Mighty Porthos suddenly cleared his throat and exclaimed "since I am the strongest man in all of France, I would face my opponents a different way. Because the View itself is the trickiest piece of code to test and maintain, I would squeeze the View with great force until the only thing left inside the View is a mere skeleton of presentation logic. I will render the Lady de Winter's greatest warrior a mere...
Passive View
Last time we looked at a small screen that allows a user to select options for configuring the shipment options for some sort of online order. We examined a sample approach utilizing the Supervising Controller variant of Model View Presenter that left the View in charge of simple screen synchronization while utilizing an external Presenter class to handle more complex behavior and all communication with the rest of the system. This time around we're going to build the exact same system, but use the Passive View variant of Model View Presenter.
It's probably easiest to explain Passive View by first explaining how it's different than Supervising Controller. The goal of the Passive View is to maximize the ability to automate testing of the screen, and that means taking as much as possible out of the hard to test View code and moving it to the Presenter. For that reason, the biggest difference is the reduced role of the View -- even from the already slimmed down View attached to a Supervising Controller. The Presenter/Controller is responsible for all screen synchronization. The View in Passive View is an extremely thin wrapper around the presentation details with next to no behavior of its own. The view probably doesn't even know about the Model classes. To start the Passive View solution, let's look first at the interface for IShippingScreen:
public interface IShippingScreen
{
string[] ShippingOptions { set; }
string[] Vendors { set; }
bool InsuranceEnabled { set; }
bool SignatureEnabled { set; }
string StateOrProvince { get; set;}
string Vendor { get; set;}
string ShippingOption { get; set;}
bool PurchaseInsurance { get; set;}
bool RequireSignature { get; set;}
double Cost { get; set;}
}
As you can probably guess from this interface alone, the View becomes simplistic. The Presenter is now responsible for telling the view what piece of information to put into each UI widget. The concrete View is going to end up looking something like this:
public partial class ShippingScreen : Form, IShippingScreen
{
public ShippingScreen()
{
InitializeComponent();
}
public string ShippingOption
{
get { return shippingOptionField.SelectedText; }
set { shippingOptionField.SelectedText = value; }
}
public bool PurchaseInsurance
{
get { return purchaseField.Checked; }
set { purchaseField.Checked = true; }
}
// Who sees a problem here?
public double Cost
{
get { return double.Parse(costField.Text); }
set { costField.Text = value.ToString(); }
}
// Stuff that we don't care about
#region Stuff that we don't care about
#endregion
}
At this point, the view is as dumb as we can possibly make it. It should be nearly trivial to verify the functioning of the View code by simple inspection -- for the most part anyway. We could easily decide to forgo testing the actual View itself at this point and judge that to be a perfectly acceptable compromise.
The Presenter is now more complicated because it's taking on the additional responsibility for synchronizing data between the screen and the Domain Model. In this case it's making a one to one transference of properties from the screen elements to the properties of the Shipment class.
private Shipment createShipmentFromScreen()
{
Shipment shipment = new Shipment();
shipment.PurchaseInsurance = _screen.PurchaseInsurance;
shipment.StateOrProvince = _screen.StateOrProvince;
// so on, and so forth
return shipment;
}
For a small screen, that's not that bad. In reality, I generally use Passive View for small screens like login screens. I do find the screen synchronization to be a chore. Then again, think about the case of a screen that serves to display and edit an aggregate object structure with multiple levels of hierarchical data. Data binding works best with "flat" objects, so you've got to do something. In my mind, sacrificing the structure of the business domain to fit the user interface tooling is mostly a poor compromise. To have the best of both worlds you can either create a wrapping object to provide a "flattened" view of the object hierarchy to allow data binding to work, or skip that and use Passive View to have better control over the screen synchronization.
What is cool about Passive View, besides the extra testability, is a further set of insulation between the presentation technology and the business and service layers of the application. When I built the Supervising Controller approach, I deliberately used an object specifically built for the data binding and hid the "real" domain behind IShippingService. This time around, let's let the Presenter work with the "real" domain classes somewhat. That being said, ShippingScreenPresenter will now interact with an IShipper class to get at the business rules for a particular shipping option.
public interface IShipper
{
bool AcceptsInsurance { get;}
bool CanRequireInsurance { get;}
string[] Options { get;}
string Description { get;}
bool CanCaptureSignature { get; }
double CalculateCost(Shipment shipment);
}
Of course, we've got to find the correct IShipper in the first place. For that, we'll use a Repository:
public interface IShipperRepository
{
IShipper[] GetShippersForLocation(string location);
IShipper FindShipper(string shipperName);
}
Even more so this time, the ShippingScreenPresenter is largely a Mediator between the interfaces exposed by IShippingScreen, IShipper, and IShippingRepository. The screen synchronization can be more work inside the presenter, but I think you can get by with less abstraction of the rest of the application. The ShippingScreenPresenter might look like this:
public class ShippingScreenPresenter
{
private readonly IShippingScreen _screen;
private readonly IShipperRepository _repository;
private IShipper _shipper;
public ShippingScreenPresenter(IShippingScreen screen, IShipperRepository repository)
{
_screen = screen;
_repository = repository;
}
public void ShipperSelected()
{
_shipper = _repository.FindShipper(_screen.Vendor);
_screen.ShippingOptions = _shipper.Options;
_screen.InsuranceEnabled = _shipper.CanRequireInsurance;
_screen.SignatureEnabled = _shipper.CanCaptureSignature;
}
private Shipment createShipmentFromScreen()
{
Shipment shipment = new Shipment();
shipment.PurchaseInsurance = _screen.PurchaseInsurance;
shipment.StateOrProvince = _screen.StateOrProvince;
// so on, and so forth
return shipment;
}
public void OptionsChanged()
{
Shipment shipment = createShipmentFromScreen();
_screen.Cost = _shipper.CalculateCost(shipment);
}
}
Needless to say, using the Passive View pretty well demands an Interaction Testing style of unit tests with lots of mock objects. If you don't grok RhinoMocks, Passive View might not be for you. Supervising Controller is a definite alternative, but in a later section I'll take a look at the Presentation Model pattern for a state-based style of unit testing.
Interlude
Young D'Artagnan looks puzzled. He finally asks his older friends "Wouldn't that make the communication between the Presenter and View very chatty? And what's to stop the Presenter from becoming just as convoluted as an Autonomous View?" Porthos snorts and exclaims "You are a perspicacious lad! I would not stop with crushing the View into submission. I will use my superior strength to crush the Presenter until it only contains a single, cohesive responsibility!"
Summary
I used Passive View quite extensively on my first WinForms project in 2004. Overall, the experience hooked me for life on using Humble Dialog Box design philosophies for building fat clients. It became routine for screens to work on the very first attempt to run new screen features -- assuming that you really did unit test the individual pieces first. We also so another noticeable trend, screens that were fatter with more logic and less unit test coverage generated alarmingly more bugs and took much more time to debug.
I mentioned that the screen synchronization can become a chore. The presenter potentially picks up more responsibilities for screen synchronization and state like "IsDirty" checks. The downside of Passive View is a chatty interface between View and Presenter. I wrote an article early last year detailing my Best and Worst Practices for Mock Objects. Most of the advice for what not to do with Mock objects came from that same project that we used Passive View. My advice is to watch the size of the Presenter. If it gets too big, split up responsibilities. Maybe you take screen synchronization, IsDirty logic, and maybe validation and put it in some kind of "inner" presenter. The "Inner" presenter talks to the View itself. The "Outer" presenter talks to the "Inner" presenter and the outside world.
One way to detect a need for this Inner/Outer presenter case is to watch your unit tests. If you ever find yourself writing an uncomfortable number of mock object expectations in any one test, you're probably violating the Single Responsibility Principle (SRP). If you find yourself setting up mock object expectations for something that's barely relevant to the subject of the unit test, you almost automatically split the class under test into multiple classes. One of the best design tricks you can apply is to continuously move your design closer and closer to SRP.
Conclusion
The four friends finished their repast and sat around the fire, sated from the provisions generously supplied by a minor noblewoman of Atho's acquaintance. Aramis, who the companions know is the craftiest of the four friends, speaks up: "slaying a View of many responsibilities with the sharp edge of a mock object is a fine thing, but I think that we can use our wits to fool the screen into subservience to state based testing by employing the Presentation Model to..."
To be continued in Part #5 - The Presentation Model -- unless you'd rather talk about something else.