We will briefly cover design pattern concepts involving declarative and imperative programming structures, and then move on to a simple hands-on example that demonstrates the design pattern concepts presented.
Declarative programming and imperative programming rely on different general programming structures to accommodate the development of a loosely-coupled, testable application. Declarative programming relies on a programming structure that has already been implicitly introduced in this documentation, namely data (or component) binding. In a binding structure, there is what is termed a source object and a target object with properties of each that are bound together.
The source object is bound to by the target object, and the source object has no explicit awareness of the target object. The target object, however, has explicit knowledge of the source component. In MVVM, the data model, or “Model”, is the source and knows nothing about the view model or view. Both the view model and the view can bind to the model. The view model itself is the source for the view and has no knowledge of the view, and the view binds to the view model. The view is thus the target for the view model, because while it is the view that binds to the view model, it is the properties on the view model that notionally drive properties on the view (even though the binding can be two-way). The reason for the “source” and “target” terminology is that when a binding is set up to make application components work together, the source could change, but the target always remains constant for a binding. Think of a data table as the view component, and an array of data objects as the source. The source could change constantly while the application runs as items are added to the array, or properties on the data items change. But while the data may change, the target view for the binding stays the same.
Thus, bindings are declared on the target, and the interfacing between the view and view model in an MVVM application is declarative, using bindings. Both the XAML markup language and the Windows Presentation Foundation are designed with this declarative programming model in mind. To show this, we will compare the imperative programming method with the declarative programming method for creating bindings. Below is the imperative, procedural programming method, taken from the MSDN documentation.
//make a new source
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
Here, myDataObject is the source object and myText is the target TextBlock object. The binding object, myBinding, is created with a reference to the “MyDataProperty” property name, and then the binding’s Source property is set to the myDataObject object. Then the target object’s SetBinding method is called, specifying which property on the target should be bound, with the binding object passed in. The “TextBlock.TextProperty” is a special type of static class property called a DependencyProperty, which we won’t get into here but is worth investigation for those interested in a deeper understanding of the WPF framework.
Now let’s compare that to a declarative XAML implementation. Here goes:
Three lines of code. One line of code really, as the gratuitous inclusion of the Canvas just provides the reference of where the TextBlock is placed inside the window’s visual tree. Here, the TextBlock is declared to be a child of the Canvas on the visual tree, and the textbox’s “Text” property is declared to be bound to the MyDataProperty property.
You might be thinking that there’s something missing here. We can see that MyDataProperty is the property to be bound to, but where is the reference to the source object that owns this property? In WPF, if the source object is not explicitly defined, then the source object is presumed to be the target’s “Data Context”. The Data Context is simply a property that all WPF framework element controls have which is the default data source object. When the data context property is read using the property getter, the object that is returned, if the property wasn’t explicitly set, is inherited up the visual tree. The framework will internally check the canvas to see what its data context is, and then go up and check the Window level to find the data context.
The design of a view is thus typically done declaratively using XAML code, and if procedural programming is required to implement view functionality, then the procedural programming is typically implemented in a “Behavior”, and the view components are then declared to have the needed behavior.
The interfacing between the view model and the model can be declarative, however typically it is imperative/procedural. This is because typically the bulk of the view model or other application modules are implemented using procedural C# code, and the procedural programming style is not explicitly designed to make binding declarations like XAML is, as we demonstrated. So while binding to the model from the view model is certainly possible, it is more cumbersome to procedurally implement and so typically binding isn’t used. It is common however for the view model to contain model components which the view directly binds to.
It was mentioned above that the primary design pattern that declarative programming uses to maintain loose coupling is with binding, where by simply changing the source object, all target properties automatically rebind to the new data context properties. For imperative or procedural programming, the primary design pattern used to maintain loose coupling is called dependency injection, or inversion of control. Dependency injection and inversion of control have to do with maintaining the “SRP” (Single Responsibility Principle) pattern and optimum dependency management. Applications become monolithic, tightly coupled, and difficult to work with typically because the interdependencies between classes become unmanageable or difficult to conceptualize. Dependency injection and inversion of control facilitate one of the most crucial requirements of a complex application: That objects depend on interfaces, and not on implementations.
Because the design of Virtuoso host applications are largely implemented by simply composing a virtual device or application from provided components using declarative programming, dependency injection and inversion of control are not fully addressed here. However they are given more comprehensive treatment in Montage, as one of Montage’s powerful capabilities is dependency management and facilitating a dependency injection/inversion of control design pattern in embedded systems. Montage’s IoC functionality for embedded firmware components is not yet released at the time of this documentation’s publication.
For further reading:
Data Binding: https://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx
Note: The published literature describing MVVM is notoriously poor at providing a clear practical differentiation between the view model and the model in an MVVM application. In application designs the model often goes unimplemented because the value of maintaining a distinct model class to adhere to the design pattern produces no apparent benefits. To assist in the conceptual distinction, the model is typically needed to represent data that is serialized and has a lifetime beyond the local application instance, for example when data must be serialized for transmission over a network to a separate application, or when data must be serialized to disk or to a database, and used between instances of the application.
The motivation for the model as a separate design element in the MVVM design pattern can be a point of confusion because of the lack of clarity regarding how the separation of the view model and the model promotes loose application coupling, since both are structurally similar in their imperative/procedural nature. In fact, the view model and model are typically tightly coupled. For applications where data is created, manipulated, and consumed all within the lifetime and scope of the individual application instance, you might not need to punish yourself for not finding use for the model pattern element.