CAB, Model View Presenter, Passive View, and Humble Dialog
In trying to wrap my head around how solutions should be designed and componentized in SCSF/CAB, I’ve spent a bit of time trying to study up on Model View Controller (MVC) and Model View Presenter (MVP).
The packaged documentation, in my opinion, doesn’t necessarily do a good job of covering these two topics and the variations of MVP that really make sense in CAB.
One of the best resources for discourse on these two topics is Martin Fowler’s article on GUI Architectures, which provides a broad view of three of the most common underlying architectural choices for many of the GUIs that we work with today.
Of note is that Fowler’s entry for MVP has been retired; instead, replaced with Passive View (PV) and Supervising Controller (SC).
What I’ve observed is that PV is more “aligned” with the design of the components in CAB than SC, which Fowler summarizes:
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.
In my opinion, since the CAB generated presenter isn’t coupled with a concrete implementation of the view, PV is the way to go since this will allow a lower level of coupling with the concrete implementation of the view. I went about this by adding interface methods to return Control (one could go as generic as Object as well) instances from the view which would then be rendered, wired, and databound by the presenter. In other words, it’s not really Model View Presenter as the documentation would have you believe.
Regardless, this allows a great deal of flexibility in the design of the module as a whole as the view can truly be replaced completely independently of the presenter since the presenter is only coupled with the interface class. In addition, it allows for easy replacement of data visualization types using the adapter pattern to connect data to the control type (for example, having one adapter if the returned control is of type TreeView and another when the control is a DataGridView).
However, an even better description of the architectural intent of CAB modules is Humble Dialog, a pattern formalized by Michael Feathers. What Feathers terms “smart object” is congruent to the presenter, which “pushes data onto the view class” through a view interface. The view, of course, is the actual UserControl derived view class. Now this leaves a key question: what is the place of the model in such a pattern? Does it have a place? With SCSF (but not necessarily with CAB when used with desktop applications), the classic sense of the model almost has no place in such an architecture; the model is but a dumb container for data. It leaves you with what Fowler terms an Anemic Domain Model, an “anti-pattern”:
The basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior, and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say that you are not to put any domain logic in the the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data.
While Fowler views this pattern with disdain, I can’t help but wonder whether it is the most natural choice for a smart client style application which should rely heavily on service layers to handle the data models. Otherwise, it would seem that one would end up writing a great deal of domain objects which were nothing more than shells (well, perhaps this is still useful if complex service interactions are necessary) which make the calls through the service proxies.