A few months ago, I put quite a bit of work into understanding an MVC framework called PureMVC and getting a team up to speed using it.
I didn’t like the PureMVC thing too much because it’s so heavy. In this post I described a strategy for doing MVC with your own, home baked MVC pattern instance.
Do you bake MVC? Here’s a way to get your three layer cake from the designer’s pencil to the forge.
Ideal means this stuff is paperwork. I haven’t really tried it – but frankly speaking it’s mostly a simplification of what goes on in PureMVC, with a little bit of salt. Bon Appetit
An ideal MVC instance
In this model, the software is structured around three broad categories instantiated as packages.
- View classes. View classes use some GUI API such as flash, java swing, OpenGL or the like. View classes also hold view specific data(1). When the user performs an action, such as clicking a button, the view handles this action by firing an event (see below).
- Model classes. Model classes hold your application data.
- Controller commands. A controller command is a command that changes the way the application reacts to events from the model and view, rather than changing the view and model directly. Typically, a controller command does nothing except register callbacks causing such or such event to trigger such or such command. Additionally…
- Model actions are commands that change the model in some way. These commands operate only on the model. When the model has changed in some way, and we want to update the UI, the model needs to fire an event
- The UI is controlled by UI update commands – commands that change the UI in some way. These commands operate only on the UI.
More about events
In the above model, note that neither view or model ever handles an event directly. This is not to avoid type dependencies, but to avoid conceptual dependencies:
- Maybe an animation just finished in the view. If the model handles this event, the model is doing something based on the fact that an animation is complete. Even though the actual view type is hidden from the model, the model now requires making a big assumption about the view to operate.
- Maybe the model changed in such or such way. It may be that this causes just one change in the view, but it maybe that the view needs to update in many ways, or it may be that we are using a general purpose view component that is used in combination with concurrent models.
Also note that a view or model doesn’t directly instantiate commands. This is consistent with the fact that the view shouldn’t know about the model (and vice versa). There are many cases in which it may be convenient and safe to allow the view to know about the model; however, this is a simplification of the ideal instance described here, so to stick to the general rule, the view should generate a SEND_MAIL event, even though it is obvious that this event maps to the SendMail command.
Lightweight versus heavyweight events
When writing your application, you will often need to send quite a bit of data along with an event – for example if a view needs to update a table, you need to send whatever needs to be updated along with your event, not just a message.
In this case, you have two choices:
- Use untyped event data. This is in some ways unsafe (more parsing/relying on implicit data formats), but it saves the trouble of defining additional structs or classes to store event data.
- Use typed event data. It is commendable to include event definition in a subpackage, e.g model.event, view.event
More about the event dispatcher
To make the above work, you need a kind of event dispatcher that can broadcast events to receivers. All your view/model classes should be able to access the dispatcher so that they can trigger events. Your control commands should be able to access the dispatcher so they can register callbacks.
It may be a good idea to expose the dispatcher to the view/model and the controller commands using a different interface. We don’t want view classes and model classes to modify our event to command bindings accidentally
ViewManager and ModelManager
When a command is instantiated, it needs to get hold of the data that it wants to manipulate. A simple way to allow this is to define singleton ViewManager and ModelManager. So for example the ViewManager contains all the views, and ModelManager is the root of the model’s data hierarchy.
How does the application start?
You may have noticed that it’s the job of command to specify bindings from events to commands. This leads to one of those chicken and egg problems – which one started it all? an event or a command. This doesn’t really matter, but here are two solutions anyway:
- Use a Main command. Then your framework needs some kind of bootstrap class that you call start() on.
- Invoke a command to bootstrap your application.
How to use the above while enforcing feature driven separation
It may be a good idea to combine your MVC pattern with some degree of feature driven separation (I wrote a lot about the F* Meta-Pattern and feature driven separation somewhere else). Here’s how a simple, non adventurous way to do it:
- Break down your controller package into subpackages – one package per feature.
- Use a controller command to register all the callbacks and commands related to each given feature. This is the ‘feature root’
- Bundle additional commands required to realize your feature in the same sub-package as the feature root.
There are pros and contras to the above approach, compared to a typical F* instance:
- PRO 1: In many existing workflows, we use WYSIWYG UI builders to generate our views. The original F* pattern typically requires moving back UI building into the code, because you need to be able to add widgets as part of feature code – in F*, your ‘send mail’ feature will require you to add the send mail button to a toolbar – that’s the price to pay if you want the UI to be correct once you have the removed the mail feature.
This is further reflected in the way UI designers work. In cool applications, the UI is typically very integrated. Removing a function often means redesign in this case. - PRO 2: In many applications, most features rely on a core data model – i.e most of the data is used by several features. F* fragments your data model across features and leaves the generation of the core model as a refactoring exercise. There are many cases in which designing the data model as a self contained entity, adding stuff to it over time, maybe more efficient.
- CON: F* allows you to delete a feature by deleting all related classes. With the above strategy, deleting the feature deletes only application functionality, not widgets and model data related to such functionality.


Comments