Friday, February 1, 2008
The Model, View, and (Virtual) Presenter Pattern
One of the greatest weaknesses of the MVP/MVC pattern is that although the the model is completely separated from the view (and vice-versa), the view in the MVP pattern has to have a concrete dependency on its corresponding Presenter in order to function. At first, there doesn't seem to be a way to completely isolate all three components of the MVP pattern from each other, given that the view has to have a reference to a concrete presenter. The presenter, on the other hand, knows nothing about the concrete view, other than the IView interface that is implemented by the view itself; it's a one way dependency, and yet, something just doesn't 'feel' right here...
...and then that's when the questions started to hit me: Does the presenter even have to be a concrete presenter at all? Is it possible to replace a concrete presenter with an interface type, and what would that interface look like? Are all presenters the same, or do they just all represent some sort of abstract concept that can't be centralized into a single common class?
My proposal for changing the MVP pattern is this: What if we convert the concept of the presenter from a concrete class to an abstract metaclass? If we change the presenter from a concrete class to an abstract implementation of a concept, we can effectively eliminate the dependency between all three parts of the MVP triad. It might sound complex, but the implementation is actually quite simple. In this variant of the MVP pattern, we can completely eliminate the view's dependency on a concrete presenter by converting the concrete presenter dependency into a collection of interfaces that the view requires in order to function.
A Quixotic Attempt
A naive approach would be to try to find a common interface to be implemented by presenters, but this poses a problem--no two presenters will ever be exactly alike. A presenter that will handle a list view won't always have the same interface as a presenter that handles a date field. In addition, there might be presenters which might hold some business or validation logic that other presenters might not necessarily share. In other words, we're dealing with a situation where each presenter interface allmust be heterogenous for each view type, and that makes it difficult to group all of the presenters into a single interface that could be used by each view type.
At first, it might seem like we have no choice but to make each presenter concrete and customize each one of them to fit their corresponding views--but what if we refactored each one of these concrete presenters into multiple interface dependencies for each view? If you think about it, a concrete presenter is nothing but a collection of different responsibilities assigned to a concrete interface. For example, a DatePresenter class might have a few methods that check if the date value presented in the view is valid. The same DatePresenter class also might have methods which can persist the date value to the database. The crux of the concrete presenter in the MVP pattern is that the date view relies on this same DatePresenter to control its behavior. There's a one-way separation between the view and the presenter, and there has to be a way to completely isolate the presenter from the view itself.
An Unnecessary Coupling
A view (for the most part) doesn't need a reference to a concrete presenter class as much as it needs a reference to the interfaces and responsibilities that each one of those interfaces represent. In fact, if I were to refactor a concrete presenter, extract each one of its interfaces, and have each one of those interfaces represent a responsibility that the target view requires, it almost seems like I've converted the concrete presenter into a de facto service container.
Each presenter responsibility corresponds to a service, and each service corresponds to an interface type. Since no two presenters are alike, one could even say that each presenter has a list of services that it offers to each view. So for me, the next logical question is this: If concrete presenters are nothing but de facto service containers, why not use a real service container (aka IoC container) and ditch the concrete dependency altogether?
If a concrete presenter is actually nothing but a degenerate IoC container in disguise, then I can take each one of the view types and replace their concrete presenter dependencies with a single dependency--a dependency on real IoC container by itself. What makes this interesting is that in this variant of the MVP pattern, the 'P' in the MVP pattern no longer exists as a concrete class. The view's concrete dependency on the presenter has been supplanted by interface dependencies supplied by an IoC container. Using this scheme, the view knows nothing about the actual presenter concrete class, and the presenter knows nothing about the concrete view class. As an added bonus, we can even separate the presenter from the concrete model by having the presenter rely on interfaces supplied by the model, and vice versa. The best part about all this is that (aside from the interface dependencies) all three parts of the MVP triad are completely isolated from one another. The only dependency that all three parts of the MV(P) pattern share is the IoC container itself, and since most IoC containers are easily configurable by design (namely LinFu), this dynamically gives us complete control over all the dependencies of a given application...
Note: So far, all of this is just theory, and I have to write it down somewhere before I forget it. I think this pattern can help quite a lot of people, and hopefully I can test it soon.