The MV(C) paradigm


slide: The MV(C) paradigm

The previous examples (describing the cooperation between gadgets, windows and events) illustrate how the complex details of the interactions involved may be hidden from the user, who merely has to define the appropriate (virtual) functions to specify the particularities of the application. In a similar way, the interactions between user-defined objects may be specified by employing a collection of abstract classes, governing the overall structure of the process of cooperation. As an example we will look at a skeleton implementation of the MVC-paradigm, which is an essential part of the graphical user interface development framework of the Smalltalk-80 system (see section MVC). For convenience, we omit the controller (C) part (which is responsible for dealing with user input). However, in the next section we will discuss how we may employ events to deal with user input in an (even more) elegant way. Omitting the controller class, the MV(C) structure consists of a model class (from which the class describing the functional behavior of the application will be derived), a view class (to display the state of the model to the user) which may be refined by the application to fit its particular needs, and one or more model realization classes defined by the user to model the functional behavior of the application. An abstract model class may be defined as in slide 7-mv-model. By employing a template class we have abstracted from the actual value type of the model.

Model -- functional behavior

  template< class T >
  class model { 
\fbox{model}
public: void tell(view* v) { dependent = v; }
one view only

void changed() { if (dependent) dependent->update(); } virtual T value()=0; protected: view* dependent; model() { dependent = 0; }
restricted creation

};

slide: A model class

The model class provides three public member functions. A function tell (to install a view object for the model), a function changed (to notify the view objects associated with the model of a change), and a function value (which is merely used to illustrate how the model object may give information concerning its state). For convenience, we assume that there is only one view object associated with a model object. (It is an easy exercise to extend the skeleton to associate multiple views with a model.) The view object is stored in the instance variable dependent. Also, the class model needs to have a virtual destructor, to reclaim the resources needed by its derived classes and their views. Now look at the definition of the view class in slide 7-mv-view. To allow for models supporting various value types, the class view must also be defined as a template class.

View -- user interface

  template< class T >
  class view { 
\fbox{view}
public: view(model* p) { p->tell(this); m = p; } virtual void update() { cout << m->value() << endl;
or whatever

} protected: model* m; };

slide: A view class

The view class provides a constructor which takes a pointer to a model object as its argument. Evaluating the constructor results in setting the dependent view pointer of the model to the view being created. When a view object receives the request update, the model associated with the view is consulted and the information delivered as a result is (in some way) displayed to the user. As an example of a concrete model class, look at the account class defined in slide 7-mv-account. It is derived from model since the value type of the account class is simply int.

Example -- account

  class account : public model { 
account
int n; public: account() { n = 0; } void operator++() { n++; changed(); } int value() { return n; } };

slide: The account class

The account class introduces an integer instance variable, an operator to increment the instance variable (just like a counter), and (re)defines the function value (which merely returns the value of the integer instance variable). There is no need to redefine the view class, although this might have been easily done. As an example of using the account class look at the fragment below:
  account a; view v(&a); a++; a++; 
  
An instance of view must be created since account is derived from model. Note that incrementing the account results in displaying the value automatically. This illustrates in a nutshell how the modification or access of a value may be propagated into a chain of arbitrarily complex (inter)actions. Yet, the abstract architecture of the MV(C) framework ensures that the interactions take place in a carefully controlled way. However, there is a serious problem with using the MV(C) paradigm in C++. Despite its elegant appearance, it is hard to generalize the relation between model and view classes to include arbitrary interactions. In the next section, we will elaborate on this by generalizing the notion of event-driven control to include user-defined events.