Here is a brief technical explanation of MVVM, which contains enough detail (borrowed from its WPF implementation) and examples to allow the reader to grasp how it actually works.
- MVVM is an architectural design pattern for building interactive applications. Its aim is to achieve complete decoupling of application logic from presentation logic.
- MVVM is not something new, and it was not even new at the time that it became popular. It was named by John Gossman in 2005 (⬀), who states that it is the same as the Presentation Model pattern named by Martin Fowler in 2004 (⬀), who in turn states that it was previously known as the Application Model pattern in certain Smalltalk circles as early as in the 1980s (⬀), and that's where we lose track of it: for all we know, it may have originated in Ancient Egypt.
- The acronym stands for Model, View, Viewmodel.
- Model refers to the main estate data model of our application; it is optional and largely irrelevant, so it will only be mentioned a couple of times here.
- View refers to the user interface.
- Viewmodel is the secret sauce, but in essence it refers to the application logic.
- Application logic is placed in objects known as viewmodels. A viewmodel does the following:
- Publicly exposes state, part of which is publicly mutable.
- Issues notifications about any mutation of its state.
- Responds to state mutations with behavior, such as updating a data store.
- Manifests its behavior by means of further modifying its own state, which in turn generates more state mutation notifications.
- This allows the following very simple workflow:
- When the presentation layer modifies a property of a viewmodel, the viewmodel takes notice and exhibits its behavior.
- When a viewmodel modifies one of its own properties, the presentation layer takes notice and updates the screen.
- Thus, a viewmodel essentially implements a fully interactive and yet completely abstract (i.e. not graphical) user interface, with mutable properties instead of editable controls. The viewmodel is free from presentation concerns such as where on the screen the properties may be shown, what user interface controls may be used to show them, etc.
- Note: GUI pushbuttons, which have no state, are implemented as special "Command" objects that are exposed by a viewmodel besides its properties, but they could also be implemented as Boolean properties, where a transition from say, true to false triggers behavior. Command objects make viewmodels more self-documenting and more usable, but they are nothing but a nice-to-have feature: in principle, everything could work with just properties.
- Viewmodels perform all the necessary transformations between the estate data model and what we want to present to the user; for example, in the estate data model we may have a person's date of birth, but on the screen we might want to see their age expressed as a number of years, so the viewmodel performs this conversion.
- If we wanted to allow the user to edit a `Customer` entity in a modal dialog box:
- There will be a viewmodel for this dialog box, which exposes:
- One property for each editable field of `Customer`.
- One command for the 'OK' button
- One boolean property which stands for the 'enabled' state of the 'OK' button.
- One command for the 'Cancel' button, presumed to be always enabled.
- The viewmodel may set the enabled state of the 'OK' command to true only once the user has made changes to the fields.
- When the 'OK' command is triggered, the viewmodel performs validation.
- If validation fails, the viewmodel instantiates another viewmodel which stands for an error message, and the view chooses how to show it, e.g. with a modal dialog box or with a temporary "toast".
- If validation succeeds, the viewmodel persists the entered information in the data store.
- If the user opts to cancel, then the viewmodel discards the edited information.
- Viewmodels are so agnostic of presentation concerns that they can in fact be instantiated without any user interface at all; instead, they can be exercised by testing code alone, by modifying their public state and examining how they further modify their public state in response. This allows us to test the entirety of the behavior of our application logic without a user interface and without having to examine any of its private implementation details such as its data store.
- The presentation layer consists of views.
- In a desktop application, views are user-defined controls, panels, windows, dialogs, etc.
- In a web application, views would be HTML fragments.
- Each view type is associated with a viewmodel type, and contains information describing how each property of the viewmodel is bound to each property or attribute of controls within the view.
- So, a `CustomerForm` view which is meant to display a `Customer` viewmodel specifies that the `Name` property of the customer should be bound to the `Text` property of a certain `TextBox` control within the view.
- Note that these associations are purely declarative, and they reference nothing but statically available information, (data types and their members,) which means that they can be described using a markup language, i.e. without the need to write any application-specific code to build up the user interface.
- The application defines which view type is associated with the root viewmodel. A view may be tasked with displaying a viewmodel which contains a property which is in turn another viewmodel. Let us call that a child viewmodel. In this case, the view can do one of two things:
- Specify a particular child view type to display the child viewmodel.
- Specify a mapping table which defines what type of child view to use for displaying any different possible type of child viewmodel.
- Views are resolved at runtime, based on the actual type of the child viewmodel, which can be more derived than the advertised type of the child viewmodel property; so, if a viewmodel exposes a `Customer` child viewmodel property which can be either of type `Customer` or of a more derived type `WholesaleCustomer`, the mapping table can specify a different child view type for each of these child viewmodel types, and the right child view will be instantiated at runtime depending on the actual type of the child viewmodel.
- Any child view can in turn contain its own mapping table which defines more associations, or redefines existing associations, so that for example:
- In the scope of an `AllCustomers` view, the `Customer` viewmodel can be associated with a `CustomerRow` view, so as to present the customer as a row in a tabular control.
- In the scope of a `CustomerDetails` view, the same `Customer` viewmodel can be associated with a `CustomerForm` view, to present the customer using individual fields laid out on a surface.
- Note that again, this mapping table consists of nothing but statically available information, (view types and viewmodel types,) so everything is still achievable in markup.
- A child viewmodel property can be optional or nullable, thus allowing the application logic to control whether an entire section of the user interface is available or not at any given moment.
- A child viewmodel property can be a collection of viewmodels, allowing for a corresponding child view which is a list control or a tab control. Viewmodel type mapping still applies, so if the collection contains viewmodels of different types at runtime, the resulting list control will consist of different kinds of rows, or the resulting tab control will consist of different kinds of tabs.
No comments:
Post a Comment