2021-01-16

The MVVM architectural design pattern

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.
  • 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 in the late 1980s (), and that's where we lose track.
  • 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 mutable.
    • Responds to externally induced mutations of its state with behavior. (This behavior may include side effects such as updating a data store.)
    • Manifests its behavior by means of modifying its own state.
    • Issues notifications about these state changes.
  • 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, and parameterless void methods instead of buttons.
  • 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.
  • Viewmodels expose information by means of properties that are meant to be seen and edited by the user, but without any presentation concerns such as where on the screen they may be shown, what user interface controls may be used to show them, etc.  
  • Viewmodels expose available actions by means of "commands" which are meant to be represented in the user interface using controls such as pushbuttons. 
    • Note that commands could also be exposed as nothing but boolean properties which perform their action when transitioning from a certain state to the other, so they are optional; they exist mainly in order to make their purpose evident and self-documenting.
  • For example, in an application which allows the user to edit a `Customer` entity in a modal dialog box with 'OK' and 'Cancel' buttons:
    • 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.
    • If validation succeeds, it 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 our entire application logic without having to do so in integration with a user interface.
  • When a property of a viewmodel changes, a notification is issued. Thus:
    • When the presentation layer modifies a property of a viewmodel, the viewmodel takes notice and exhibits behavior.
    • When a viewmodel modifies one of its own properties, the presentation layer takes notice and updates the screen. 
  • 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 a property of attribute of some control 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 consist of nothing but statically available information, (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 user interface code.
  • 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