The Model-View-Controller Architecture

There are several problems associated with developing graphical user interfaces:

1. GUIs are large and complicated. It's not uncommon for a GUI to represent between 30% and 60% of an application's source code and programming effort [SHN].

2. GUIs are usually platform dependent, hence must be rewritten each time the application is ported to a new platform or window library. Probably the main feature behind Java's popularity is that Java GUIs are platform independent.

3. User interfaces tend to be the most volatile part of an application. Changes to the user interface are far more frequent than changes to the application logic. Also, the same application layer might need several different user interfaces.

4. User interface design is a specialty. Not only do user interface developers need to know about ergonomics and psychology, they also need to know about window and stream libraries.

With these problems in mind, probably the most important principle in user interface design is that the module encapsulating application data and logic should be independent of the user interface module. If the two modules are tightly coupled, then changes to the user interface might propagate to the application module. Porting such an application to another platform or window library might require rewriting the entire program! Of course this principle also applies to applications with console user interfaces.

Independence of presentation logic and application logic is even important at the level of individual functions. For example, consider the following definition:

double cube(double num)
{
double result = num * num * num;
cout << "cube = " << result << endl; // bad idea!
return result;
}

While the "cube = ..." message might be considered user-friendly when cube() is called by a human, it becomes annoying screen clutter when it is called by another function, which is by far the more common case. This severely limits the reusability of this function. For example, we would never want to use this function in an application with a GUI. To put it another way, cube() tries to do too much. It not only computes the cube of num (application logic), it also communicates with the user (presentation logic). It would be more reusable if it left presentation decisions to its callers.

The Model-View-Controller design pattern formalizes the presentation-application independence principle:

Model-View-Controller [POSE]

Other Names

MVC, Model-View-Controller architecture, Model-View architecture, Model-View Separation pattern, Document-View architecture.

Problem

The user interface is the component most susceptible to change. These changes shouldn't propagate to application logic and data.

Solution

Encapsulate application logic and data in a model component. View components are responsible for displaying application data, while controller components are responsible for updating application data. There are no direct links from the model to the view-controllers.

Static Structure

Note that navigation from views and controllers to their associated model is allowed, but not the reverse. A model is independent of its views and controllers, because it doesn't know about them:

Clearly models are application modules, while views and controllers belong to the user interface module. Typically, a view is a special kind of window that displays application data, while a controller is a menu item, tool bar button, text box, scroll bar, or other component that accepts user inputs. Decoupling views and controllers is a bit anachronistic, because there are usually a small number of views but a large number of controllers, so we often speak of view-controllers as a single component. Although the Model-View-Controller pattern is typically applied to GUIs, we will see that it can also be applied to CUIs.

For example, a model for a spread sheet might be a two dimensional array of cells containing data and equations for a family's budget:


Budget

The user can choose to view this data in many ways: bar charts, pie graphs, work sheets, etc. In addition, sub windows such as menus and tool bars contain controllers that allow the user to modify the budget's data. Here's a possible object diagram:

To take another example, a model component for a word processor might be a document containing a chapter of a book. The user can view the document as an outline (outline view), as separate pages (page layout view), or as a continuous page (normal view):

The Redraw Problem

The Model-View-Controller pattern presents us with another design problem: How will views know when it's time to redraw themselves? (A view's draw() function must be called when the application data changes.) For example, if the user changes the spread sheet budget data by pushing a button on a tool bar, then how will the views be notified that the budget data has changed and that they should redraw themselves? How will the bar graph and pie chart windows in our object diagram know to call their draw() functions? Clearly the model can't tell its views to redraw themselves because it doesn't know about them.

This is a classical event notification problem. Any of the event notification patterns described in Chapter Two can be used to solve it. For example, we can make models publishers and views subscribers:

When a new view is created, it subscribes to the model that is currently open for editing. If a controller subsequently changes the model's data, the inherited notify() function is called. This calls the update() function of each registered subscriber. In the pull variant, the update() function calls the draw() function, which fetches the model's modified data, then uses this information to redraw itself: