The M/VC Antipattern
Introduction
M/VC may be more familiar as "MVC," or Model-View-Controller the design pattern used in the Smalltalk environment [KP88] and cemented as the "Observer" pattern in Design Patterns [GHJV95] by the Gang Of Four. In real life, subconsciously plowing along with M, then V, then C often leads to MV with little or no decoupled C. Call this M/VC. With controller code woven deeply into your view, it becomes nearly impossible to later switch the view out.
Java applications written using Swing and now being ported to Eclipse are an example of how one can be forced to confront this. If you've got a substantial M/VC application, good luck. With Swing view code mixed with controller logic, porting your application's view to use SWT widgets can almost require a complete rewrite (assuming you don't want to run Swing inside of SWT). If this has happened to you, it should come as no surprise; MVC has been around for some time now.
Coupling-Balls, Headed for Hell
The natural progression
towards M/VC inflexibility is easy enough to follow. Someone gives you
some screen mock-ups and asks you, "Can you do this?" With some idea
of what needs doing, you set out to design the guts of the thing. You
scribble notes on the mock-ups about how pressing this or that button
will cause this or that view to appear. Very quickly you begin to
prototype it (at least, that's what you call it). You want to quickly
throw something together to show evidence of progress; speed and lots
of early feedback are of the essence.
As you slam out your prototyping code, you implement a rough version of the model. This is naturally decoupled because doing so outlines basic boundaries in which your application will work. You need this scoping in order to proceed efficiently.
As you move beyond coding the basics of the UI you naturally begin to write controller logic in order to wire the view to the model. In doing so, you run roughshod over best practices, implementing anonymous inner classes, hard-coded strings, and whatever shortcuts you have the ability to execute. Writing quick-and-dirty controller wiring in your view is the shortest path and it works, so that is exactly what you do. So far, no harm, no foul.
Except, if what you just built really was a prototype, you'd be
throwing it away and rewriting it as a real, test-driven development
effort. Not you; your prototype is good enough code and, with a little
help, it can be great. Besides, you refactor. You'll just refactor
your way into decoupled goodness later, when there's time to do it
right.
The Incentive Problem
But this is unrealistic and here's
why: odds are you won't actually get the chance to refactor your way
into a good architecture. Even if you commit to doing this sooner
rather than later, the motivation to refactor already 'working'
prototype code is simply not very strong. This may be partially
because as you were flying through your prototype, you skipped writing
unit tests. Subconsciously, you know you cannot refactor with
confidence.
It may be because doing a satisfactory job of writing a UI app even slightly more complex than a simple form is hard work, even if just a prototype! You may not have the incentive to decouple your V from your C because it's still early in the development process and you're good; you actually can get away with adding lots of new view/controller code and still keep things manageable. You continually tell yourself that you'll refactor later. The problem is the longer this goes on the less you'll be inclined or even able to decouple the V from the C.
It's best to take what you've learned and do it right from the beginning, throwing away your prototype as originally intended. Otherwise, the greater motivation is to make a deal with the devil, fill out unimplemented features in your prototype and, along the way, somehow try to turn your prototype into 'real' code. By proceeding down this path, you make your second and final mistake. But it doesn't have to be this way.
Preserving view swap-ability is essential and is best done from the outset. This isn't to say that you wouldn't necessarily have success writing your M/VC app. You may. It may even end up as a highly usable one. The problem is that it won't have a long lifespan since view technology tends to change rapidly and be platform specific. Swing purists argue cross platform benefits and may claim this as a mitigating alternative but, even then, not taking full advantage of the OS is its own issue (as, recently, the Eclipse SWT team has reminded us).
MCV: A Better Way
As the newly ordered MCV insinuates, write controller logic after writing the model, before writing your view. Do this preferably as a test-driven development process. This is a useful technique for several reasons:
- Your unit tests can drive all of your model and controller functionality
- It forces you to put all controller logic into non-view code (there is no view yet)
- If forces you to consider separately how the controller will interface with any view
- It minimizes or even eliminates controller coupling to one particular view
MCV capitalizes on the same timing approach that Test Driven Development (TDD) does: order your effort in such a way that forces you to do the things you would normally put off entirely. If you're new to TDD, come out of your cave and see what's happening; it's good stuff. With TDD, you write tests for code that haven't been written. This is a gentle way to motivate the creation of working tests for most of your code [Ast03]. It also forces you to think programmatically about what it is you're going to build, usually resulting in more efficient and usable implementations. Like TDD, MCV also encourages this style of 'programming by intention.'
At the end of Dr. Seuss's Green Eggs and Ham, Sam I Am suggests in exasperation, "You will like it; Try it, try it; You will see." He's right; try MCV, you will like it, you will see. Then, when discussing MCV in conversation, presentations, or on your wedding night, just put it out there and let that someone ask, "Say, didn't you mean MVC?" Of course, you'll be excited to respond by saying you're glad they asked but, "...no, it's MCV; let me splain..."
References
[Ast03] Dave Astels. Test Driven Development: A Practical Guide. Prentice Hall, Upper SaddleRiver, NJ, 2003.
[GHJV95] Erich Gamma, Richar Helm, Ralph Johnson, and John Vlissides. Design Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1995.
[KP88] Glenn E. Krasner and Stephen T. Pope. A cookbook for using the model-view controller user interface paradigm in Smalltalk-80. Journal of Object-Oriented Programming, 1(3):26-49, August/September 1988.
Interesting blog. I am interested in improving, and learning more about design patterns in general. I have started a wiki to try and gather others like me.
check it out if u get chance.
http://www.codecraig.com/wiki
Posted by: codecraig | April 20, 2005 at 03:46 PM
Interesting thoughts. I'd like to see this fleshed out into a bigger article with some examples.
Posted by: Alex | April 21, 2005 at 01:24 PM
Good article. You're right that following MCV is the correct way to write unit tests -- more specifically, write tests for the controllers, written from the perspective of the View code. Once the test(s) are passing then write the controller's View code.
Hope you don't mind the shameless plug, but there's a chapter on the subject (tied in with combining TDD with use case driven UML modeling) in my book "Agile Development with ICONIX Process" - more info here: http://www.softwarereality.com/AgileDevelopment.jsp
Posted by: Matt Stephens | April 22, 2005 at 05:12 AM
This comment by in Javalobby was worth inserting in the discussion. Here's the comment and my reply:
> To me, the concepts of model, view, and controller
> are generally agreed upon. The roles of model and
> view, are even fairly commonly defined.
>
> The role of the controller, to me, has been the cause
> for a lot of discussion.
>
> Nearly everyone seems to agree that no operations
> made in the view, should be reflected in the model,
> except through the controller.
>
> However, some prefer to view the controller as
> sitting between the view, and the model. This appears
> to me to be Benjamin's view as well. I believe this
> pattern is sub-optimal.
>
> I prefer to diagram the model, view, and controller
> as a triangle; wherein all links are bidirectional
> except between the model and view. In
> many cases, allowing the view direct, read-only
> access to the model both greatly simplifies
> the design of the controller, and greatly
> improves the performance of the view.
>
> I agree with Benjamin that a common problem is that
> developers couple the view and controller very
> tightly. This is generally a big mistake. A classic
> example being exploited all over the internet is when
> a web page validates user input before sending it to
> the server. This is particularly vulnerable to
> someone using using something as trivial as telnet,
> to mimic a browser, and provide data outside the
> bounds expected, and receiving results that were
> never intended to be released. Nearly every
> major web commerce site has fallen victim to this
> attack.
>
> Personally, I find his terminology oddly backward
> however. Rather than calling the tight view
> controller coupling problem MV/C, I think it is lot
> more intuitive to call it M/VC.
I agree; the model and view should never directly communicate. Perhaps I should've clearly stated as much in my post. Also, in no way do I believe views cannot talk directly to controllers as you suggest. How they do so is the crux of the matter.
Your point about view -> controller linkage as useful or even necessary for optimization is so true that it also true for implementation.
The standard mechanism for view -> controller interaction is registering the controller as an interested view listener (or 'observer'). Here, you'll find each view requires specific listener types, quietly leading right back to a view-specific dependency in the controller code. There's no way around this. However, the coupling damage can be minimized.
Intentionally minimize the view -> controller coupling by abstracting the listener (bridge) and have the controller return the right listener impl depending on the view it is managing (factory). This part is often forgotten but, in my opinion, one of the more important aspects of getting MVC right.
My post simply points out that coding the controller before writing the view (and calling it MCV!) naturally encourages what I describe here - at least much more so than view-first.
Thanks for the excellent comment!
Posted by: Ben | April 22, 2005 at 01:33 PM
Interesting. I agree with Alex. This sounds like a great topic for a longer article with some actual TDD code examples.
Posted by: Rob Harwood | April 22, 2005 at 01:43 PM
Hmm, the comments about prototyping and not getting a chance to refactor to fix the design/architecture sound all too familiar... the Big Ball Of Mud (http://www.laputan.org/mud/mud.html) strikes again. Will we (software designers, our management) ever learn?
Posted by: djb | April 22, 2005 at 04:11 PM