MVC Principles and Practice
MVC means different things to different people. While the architecture has been implemented many times by many frameworks, no two implementations of MVC are alike. This stems from the fact that MVC is conceptually rich, but leaves much open to interpretation in order to preserve generality.
SFL has produced another implementation of MVC and with it, another interpretation of its concepts. Our objective has been to produce a modern, highly flexible implementation of MVC with minimal framework and platform dependencies. Below is a set of principles and best practices that we’ve upheld in the design of the core MVC classes and that you should continue to uphold in your application of them.
Minimize Coupling
The MVC triad contains both strong (derived type) and weak (base type) references. It’s important that you know which are which and adhere to them. Obviously, the view and controller classes must have strong references to the model – they exist to exercise the model’s domain-specific queries and commands. However, the model should never strongly reference its views or controllers. A model should export its services through queries, commands and notifications to any interested party. Therefore, the view and controller should have one-sided knowledge of their model’s capabilities.
Avoid “Positional Awareness” in the Controller
It may seem logical to assume that the class that receives mouse events, the controller, should also perform hit testing. In most cases, this is a mistake. Misplacing hit testing logic in the controller mandates tight coupling with the view, which counteracts the value of their separation.
The view is the one that computes and renders the display, so it already knows where things are on screen. So logically, it should perform hit testing. When the controller gets a mouse event, its first order of business is to request a hit test from the view. The view returns the hit object, and the controller decides what to do with it. This approach frees the controller to focus on behavior only, resulting in simpler logic and less coupling to the view. Moreover, it affords the flexibility to swap out one view for another without impacting the controller.
Use Interface-Based Programming Techniques
MVC and interface-based programming techniques are a powerful combination. By using interface-based programming techniques to realize the MVC triad, coupling and complexity are reduced. For example, rather than have the view and controller depend directly on a particular implementation of a model, they can depend on an interface which the model implements. So, any subject that implements this interface can serve as a model to this view and controller pair.
This does not refer to IDL interfaces that can be made remote. Within the triad, native language interfaces are easier and more flexible to declare and use. Refer to Chapter 3, “Interface‑Based Programming,” for more information.
Use Commands to Define the Model’s Services
Again, the model defines queries, commands and notifications. Command, in this case, means the command design pattern, also known as functors. You could choose to implement the model’s services in terms of public member functions. However, that approach doesn’t provide for record keeping. For example, you might want to create an audit trail of services rendered by the model for undo and redo or analysis purposes. Commands facilitate this, because they essentially transform functions into objects, which can store parameters and execution results. Commands can be executed, unexecuted, printed and stored. What’s more, because commands are objects, they can double as the notification. In other words, the command is both the executor of change within the model and the messenger of change to all views.
So, a model should define and export a command dictionary, which is a set of classes that operate on the model’s state. The controller imports this dictionary and triggers execution of one or more commands in response to some event. After the command completes its execution, the model forwards it as the notification of change to all views. Finally, the views can inspect the type and state of the command to determine how they should respond.
Exploit Hierarchical Decomposition
MVC scales from components to systems. For example, you can base a single control on MVC or an entire 3-tier application. This is because MVC is inherently hierarchical, allowing small systems to be composed into larger ones. Other MVC adaptations have failed to recognize this, and precluded any ability to nest – MFC’s CDocument, for example. A model should be capable of composing and observing multiple submittals. A view can be composed of many sublevels, which are attached to submittals. Moreover, a controller can nest subcontrollers, sometimes called tasks, delegating control as appropriate.
Distinguish Between Architecture and Technology
Why should you use MVC when there are already so many frameworks that take full advantage of the latest technologies? The answer is, MVC is not meant to be yet another framework, rather it is designed to complement and extend existing ones. MVC is about architecture, whereas most frameworks are concerned less with architecture and more with technology and platform-leverage – ATL, for example.
Of course, both are necessary, but it is certainly advantageous to keep the architecture and platform-dependencies separate and distinct. Very often, the technologies used to implement a system become an integral part of its architecture, which can limit flexibility. However, to the extent possible, your architecture should abstract and encapsulate the technical and platform details so team members can effectively specialize. Moreover, this focus on architecture and encapsulation helps to reduce complexity, while minimizing and localizing the impact of shifting technologies.
Although achieving a separation of architecture and technology is hard, MVC can help. Think of each triad as a completely self-contained entity. The triad needs only a host to occupy and it can do the rest, because it is container-independent. This container-independence requirement is key, because that is where many technology and platform dependencies live. Conversely, it places several constraints on design; any tight coupling to the host must be eliminated. For example, you can implement the view as a derived window or as a rectangle that is aggregated by a window. The first approach is typically used (MFC’s CView, for example, derives from CWnd), but the latter approach is much better. A rectangle that simply draws itself knows nothing of window types or platform specifics, yielding platform independence without the usual compromises in appearance, power and flexibility. In addition, a view of this form is lighter and can be hosted anywhere.
The same can be said of the controller and model. The controller requires events, but doesn’t care how they are delivered. Since the model begins as a platform independent abstraction, we need only to keep such dependencies out of its interface. In general, the platform-centric framework should aggregate and host an MVC triad. The hosting involves delivering events to the triad and giving it space to render to. This design makes your code more architecture-centric and less platform-dependent. Even if that’s not a goal, you’ll certainly appreciate how this encapsulation of implementation details tends to simplify your code.
Capture the System in the Model
Because the MVC triad is composed of three collaborating parts, it may seem natural to think of the entire triad as representing the “system” or component. And therefore, that the implementation of the system’s capabilities can be hosted in any class in the triad. However, this is not a good design choice. The model should be the one class that represents the system and exclusively responsible for encapsulating its state and functionality. The viewport and controller classes are simply clients to the model that render its state and request its services. The model should be defined as a software-IC that can be embedded within an MVC triad or exercised programmatically by a batch-oriented client.
Before designing the interface to your model, view and controller class, consider what the system is that you are modeling. Identify the system in terms of what capabilities are part of its services and what capabilities are not. Then, design the model class so that it captures all of the system’s capabilities. But, of equal importance, design the view and controller classes so that they capture none of them.
Use MVC as a Widget Architecture
What if you are developing a tree control, or a list control. Is MVC useful for such small-scale widgets? The answer is yes. MVC applies equally to application architecture and widget architecture concerns. Used as a widget architecture, the MVC model encapsulates the state and functionality of the widget. A tree control, for example, can add nodes, remove nodes, expand and rename nodes, and so on. These are examples of functions that would exist in the model of a tree control. And since the functionality of the tree control will be completely and exclusively contained in its model, the model’s interface becomes the primary programmatic interface to the tree control.
Distinguish Between Graphical and Non-Graphical Systems
A model is a software analog for a real system with state and function. But, what types of systems should a model – well, model? Actually, any system that has a well-defined and self-contained function can be modeled, no matter how large or small. One might model a nuclear reactor or a clock. But these are obvious. Less obvious are the graphical systems, such as diagramming applications or spreadsheets. In these cases, most of the function and state is graphical. So, how do we delineate, without ambiguity, between model and view responsibilities?
The reality is that there are two independent systems in these scenarios, one graphical and one not. Often, this fact isn’t recognized and they end up combined into one model or triad, which can lead to ambiguities in the design. But, graphical systems are systems too and should be modeled separately, through a presentation model.
A presentation model is a model whose purpose is to abstract and serve the functionality of a graphical system. Like other types of systems, graphical systems come in all sizes. Consider a user interface system such as a tree control. A tree control has a well-defined, self-contained purpose and can be embedded within larger systems. Its model possesses state (hierarchy data, for example) and function (such as add, remove, and expand nodes). And the tree control’s MVC triad constitutes a system that can be embedded within larger MVC triads.
Figure 12 – The MVC triad with a presentation model
Figure 12 depicts an MVC triad with a presentation model. The presentation model represents the model component of the MVC triad, serving view and controller requests. However, it isn’t the only model in this scenario. There may also be one or more system models. A system model abstracts some external (perhaps physical) system. The presentation model’s job is to implement a separate visual system, which serves to present the underlying system’s state and capability.
Now, consider a more sophisticated graphical system such as a schematic editor. A schematic editor enables you to visualize and design electronic circuitry. Both the schematic editor and the system under design are independent systems and should be modeled independently. The circuitry’s model would contain information such as the physical chips, connections, and timing properties. The schematic editor’s model will store the visual counterparts to each chip with the queries and commands to add, remove, stretch, connect them, and so on. Commands on the schematic editor’s model may implicitly invoke commands on the system model (adding a component). Lastly, upon receipt of a change notification, the schematic editor presentation model will perform reactive processing and potentially broadcast its own notification.