model view controller
Keep your UI in sync with your data.
The model-view-controller design pattern.
PNG
SVG
The model view controller Design Pattern
Frequency
Complexity

The Model-View-Controller pattern — or MVC for short — is quite old, having been first described in the 1970s, and although its original design is no longer used as widely, there are many variations, so you're likely to encounter it in one form or another.

The pattern was originally described to solve a very common development problem:

In larger GUI applications, there is a particular risk that the business logic and the graphical interface become so tightly coupled and interwoven that it's impossible to change one part without the other, making the system almost unmaintainable - a so-called big ball of mud. Following the data flow in such applications can become really challenging.

An unstructured UML diagram where various UI classes are interacting directly with business logic and vice versa
A Big Ball of Mud: The user-interfaces interacts directly with the business logic and vice versa. The missing structure makes this application hard to maintain.

The MVC pattern provides a solution: It describes a way of splitting applications into different components, with each component being responsible only for a specific concern. At the same time, coupling between these components is reduced to a minimum. This allows for more independent development and testing, making the application easier to maintain.

In contrast to most patterns in this catalog that work on class-level, the parts of MVC do not describe individual classes but represent entire concepts or roles that may include several classes at once.

Let's have a closer look at that structure.

Roles in MVC

In its original form, MVC consists of three roles, ModelView, and Controller, and each object in an application typically belongs to one of those roles.

A view, a model and a controller class interconnected.
The Model-View-Controller pattern consists of three separated parts: The model, the view and the controller.

The Model

The Model represents the core of every application, containing all the business logic, domain concepts, and rules. Depending on the type of application, this layer can be huge and include a complete implementation of the world's financial system. Yet, it could also be a single integer if your app is only a simple counter.

A rule of thumb is that classes in the Model should be totally independent of their graphical representation. While this applies to most applications, there may be exceptions when the rendering is an integral part of your domain. Think, for instance, of a diagram drawing tool or a 3D modeler, where an element's position, size, or color is essential to your application's core logic.

However, what's undoubtedly true is that your model should best be independent of a specific user interface technology. To check whether this is the case, consider what code changes you would need if you replace your complete UI library — not that you should do that, it's just a thought experiment! If switching your UI without touching your model's code (or at least most of it) would work, then you're on the right track!

Another rough guideline to determine whether a class and its data should belong to the model is that if you have to persist it in your database, there is a high chance that it is part of the model (keeping aside user-related settings that affect the UI, which you usually store differently).

The View

This layer includes all classes that are part of the graphical user interface, like widgets, buttons, input fields, and all types of user controls. To render such interfaces, you often rely on native UI libraries specific to a particular operation system, but cross-platform solutions like Flutter, Qt, or browser-and web-based systems are also available.

In the original concept of the MVC pattern, the sole purpose of the view was to render a visual representation of the underlying model, while any user input was handled by the controller.

A window with different user controls to reflect the state of the application settings.
A view composed of various user controls, like buttons, inputfields or sliders. Each control reflects the current state an application's property.

The Controller

Describing the Controller's role is more difficult, as its purpose has changed over time and is a bit ambivalent today. Initially, the Controller was responsible for receiving and processing user inputs and altering the model accordingly. In the original concept, the View itself was not directly involved in the user interaction. Having the Controller layer handle all inputs has some advantages, as it allows for testing and automating user input without the user interface, which makes testing way easier.

However, today, handling user input is mainly done by the UI and has thus moved into the View, making the Controller somewhat redundant. As we will later see, this is also why current variations of this pattern have replaced the Controller layer with a different concept.

Interaction

Opinions diverge regarding how the interaction between the three parts of MVC should be organized, but let's start where there is consensus:

As already noted, the Model is ideally totally independent of the View and the Controller and should not hold any direct references to them. Instead, model updates should best be propagated indirectly, e.g., through the Observer pattern.

When the view receives such updates, it rerenders its controls to reflect the model's new state. Likewise, controllers can also react to model updates to adapt their input processing according to the model's current state. This can be useful if, for instance, your Model changes into a read-only mode, and the Controller wants to disable all user input.

A model class with two dashed connections to a view and a controller, indicating that the model notifies the others whenever it chagnes.
The Model does not have any direct connections to the View or the Controller. Instead, it notifies them of updates, e.g., by using the Observer pattern.

In contrast, the connection between the View and the Controller is not always clearly specified. In some implementations, the View* receives the user input and delegates it to the Controller. In others, the Controller receives the input directly. The first variant may be more common today, as most user controls now handle user input directly.

Also, some MVC concepts connect the Controller to the View, allowing for directly updating the View after a user input - for example, to highlight a list view item the user just selected.

A Controller and a View interacting with each other.
In some MVC variants, the View directly delegates user input to the Controller. Likewise, the Controller often has a direct reference to the View to update it directly.

In summary, while there's little debate that the Model should stay isolated from all other layers, how you connect the Controller and the View is up to you and depends on your specific needs and context.

Notes on Implementation

Push or Pull Observers

When implementing the Observer between Model and View, your notification object can already contain all relevant update data for the View, allowing for a better separation between View and Model. Conversely, it may only contain a value identifying the type of update, requiring the View to query the Model directly to gather more information about its new state. The latter option may be easier to implement but creates a stronger coupling, as your View needs to keep a reference to the Model.

A Model that notifies a View, with the View then requesting the Model's application state.
A view keeping a direct reference to the Model to request its state when the Model sends an update notification.

Where to store the View State

Although the model is responsible for managing the application's state, sometimes state information is not directly related to the business domain but is more specific to the view. The selected items in a list view, the coloring of validated text input, or the filter criteria a user entered into a search bar - all that information is only relevant for the actual view, and storing them in the Model makes little sense.

Keeping such information - also called View State - in the View or Controller is often the better option. However, the MVC pattern is not very clear about how to best implement this while still keeping both layers decoupled. The MVVM pattern is a variation of MVC that provides a better solution here by making this additional state an explicit part of the pattern.

Input Validation

With different layers, deciding where input should be validated is often not trivial. Consider the example of a user entering their email address to register an account. This process typically involves multiple validation steps on different layers. Ensuring the email is formatted correctly can be done at the View or Controller level, but verifying if the address is already used requires access to the database and must hence be done by the Model.

Distributing these checks over the layers has the advantage that early validation in the UI can prevent unnecessary traversal through the system and reduce latency. Still, it also means that your validation logic is scattered throughout your application, making it harder to maintain. Finding the right balance here may not be easy and depends again on your specific requirements.

Variations

Over time, some variations of MVC have evolved to better handle some of its pitfalls. We will look at some of them and see how they work and differ from the original concept. Also note that this list is far from complete, as many other MVC-like variations exist, some even tailored to a specific language or framework. Nevertheless, knowing the basic concepts of the pattern can often help to understand those variations, too.

MVP

The first alternative we look at is the Model-View-Presenter pattern (MVP). Instead of the Controller, this pattern has a Presenter that mediates between the View and the Model.

As MVP doesn't have a Controller, the view is responsible for receiving user input and delegating it to the Presenter. After that, similar to the Controller, the Presenter triggers changes to the model.

In contrast to MVC, the model updates are not propagated to the View but are also received by the Presenter, where the data is formatted and sent to the View.

Hence its name - it presents the Model to the View.

A presenter connected to a model and a view.
The Presenter in the MVP pattern mediates between the View and the Model. User actions are routed from the View to the Presenter (1). The Presenter updates the Model (2), recieves the update (3) and presents it to the View (4).

The MVP pattern addresses the problem of where to store the ViewState by enriching the Model's state with additional formatting information before presenting it to the View. It also removes any coupling between View and Model, as the Presenter handles and delegates all updates.

On the drawback side, the Presenter's mediating role also requires a considerable amount of logic, making its implementation complex and harder to maintain.

MVVM

The Model-View-ViewModel pattern is another variation of MVC used in various frameworks like WPF or Angular. Like the MVP pattern, MVVM replaces the Controller with the ViewModel, a mediator between View and Model. However, contrary to MVP, the Viewmodel does not update the View directly but uses a technique called data binding to interchange data with the View. Data binding is an implicit synchronization mechanism that updates View-properties automatically when their related ViewModel-properties change. Depending on the concrete implementation, the ViewModel does not need a concrete reference to the View, allowing for loser coupling - in contrast to MVP, where the Presenter has to call the View directly.

This approach simplifies testing significantly, as writing tests for the ViewModel doesn't require mocking the View.

A view connected with to a viewmodel, which is connected to the model.
In MVVM, the user triggers commands received by the ViewModel. It updates the Model and propagates the changes back to the View via databinding.

While this works well in most situations, there are cases where binding alone is not sufficient, and the ViewModel still needs a direct interface to the View to update it correctly. Also, since binding code is often implemented in the View layer, you sometimes cannot use your native programming language but instead have to rely on languages like XAML or Angular template syntax, which, while still powerful, have limitations compared to general purpose programming languages.

By default, most data bindings are unidirectional, sending data from the ViewModel to the View. However, two-way bindings—where changes in the View are also propagated back to the ViewModel—are also supported. Although very convenient, this approach can make the data flow harder to follow, as updates can now travel in both directions.

Patterns like Flux, which we will look at next, take a different approach.

Flux/Redux

Flux is a pattern that follows a strictly uni-directional data flow. Any interaction the user triggers results in an action sent to a Dispatcher. This Dispatcher is responsible for updating the model (called Store in Flux), which notifies the View about the update.

Compared to MVC and its variations, the flow of information in Flux is much more simplified, as it always starts at the View and goes straight through the Dispatcher and the Store back to the View again.

While the Dispatcher may take some roles of the Controller/Presenter, its implementation is more straightforward, as there is no bidirectional communication with the View. It just has to trigger the Store update according to the dispatched Action.

A view sending actions to a dispatcher that are then processed by a store. The store sends its changes back to the View.
The Flux pattern uses a unidirectional data flow. Actions are triggered by the View (or external soruces) and processed by the Dispatcher. Stores are notified by the Dispatcher and update their state. These updates are then propagated back to the views.

The Redux pattern is similar to Flux, but with the main difference that in Redux, the Store is immutable. Instead of manipulating the existing application state, an action triggered by the user always results in a new state derived from the current state. This is handled by a Reducer - a pure, side-effect-free function that takes an action and the previous state and reduces them to the new state. Hence the name Redux.

While the uni-directional data flow and Redux's immutable state make it easier to follow the application logic, both patterns create some overhead. For every action the user triggers, an action and a reducer/dispatcher must be implemented. However, there are often third-party libraries that can help reduce the boilerplate code.

References