Most software development tools offer the programmer an application framework that provides the basis upon which an application can be built. Modern graphical user interfaces provide a high degree of bandwidth for the end-user to interact with the application and consequently this increases the complexity of the interface code. A framework helps to ease this burden of complexity by providing pro forma classes with much of the base level functionality already built in. The programmer can then incrementally add his/her own application-specific functionality either by subclassing or by combining pre-existing classes in new ways. The existence of the framework substantially reduces the amount of new code that has to be written.
Dolphin Smalltalk uses a unique framework called model-view-presenter or MVP. In many ways it is similar to other components based architectures but we believe it has a flexibility that well exceeds that of the most popular rivals. Naturally, one should expect that this added flexibility does also increase its complexity, and this is true, but you should find the extra capability for reuse far outweighs the additional time spent on the initial learning curve.
Although you can use Dolphin to build applications not based on model-view-presenter (the tutorial Experiments with Simple Windows is one example), you'll find that all of the Dolphin development tools are built on this framework. As we discuss the various elements of MVP below we'll draw examples from the development tools so you can see how MVP is used in practice.
When you are ready to create your own applications with MVP follow the Application and Model-View-Presenter patterns in the pattern library.
Applications built on MVP consist of triads of cooperating classes.
As you can see, each triad consists of three elements, a model, a view and a presenter.
A model is typically a domain level object, perhaps sometimes known as a business object. It holds application data and provides methods to consistently access it. Typical model classes might be InsurancePolicy or PersonalAccount. It is not necessary for a model class to support a predefined protocol of messages so it is not therefore necessary to subclass a model from a particular superclass. However, the Dolphin class hierarchy provides class Model as a suitable parent (mainly for ease of recognition) if you choose to use it.
Example: take a look at SmalltalkSystem which is a model class used by Dolphin to represent the development system itself. It contains methods to perform many of the operations that the development tools require and most of the tools use the singleton instance of this class as their model.
A model should have no association with user interface issues. This allows it to be reused in new situations which may have completely different user interface requirements from those first envisaged. Consequently a model has no direct knowledge of the existence of either the view or the presenter in the triad. Effectively what this means to the programmer is that you should never have an instance variable in a model that holds a reference to a presenter or view or indeed any other user interface object.
A view is a window that is responsible for displaying a model's data to the user. It is typically, but not definitively, a Windowsä control. In the diagram above, the red link indicates an indirect binding between model and view such that the view is an Observer onto the model. As we have mentioned, the model should not hold a direct reference to the view, but in order to inform its observers when it has changed it triggers a notification event (using #trigger:). The view will previously have registered an interest in this event (using #when:) and, when the event is triggered, in its handler method will fetch the new model data and present it to the user appropriately. It is a fundamental stance in MVP that views know about their models but not vice versa.
Example: Take a look at BasicListAbstract>>model:. You'll see that, when a model is being connected to it, this abstract superclass of a Listbox view registers an interest in several events triggered by the model. The event handler for #listChanged is registered as #onListChanged and, if you look at this latter method, you'll see that is implemented to refresh the view's entire contents.
A view has direct access via instance variables to both its model and its presenter.
While a view is responsible for the output of model information to the user, a presenter's responsibility is to manipulate the model as a response to the users input. The user gestures (list selections, mouse movements, keyboard hits) are received by the view and passed to the presenter. Normally a presenter will maintain a selection that identifies a range of data in the model that is current, and the gestures received from the view will be used to act on this. Note that, sometimes a view will adapt low-level gestures into higher-level commands before informing the presenter. An example of this occurs when using drag-and-drop or commands generated from pull down menus.
The presenter is allowed to know about both its view and model and this fits with its role as mediator between the two.
Obviously, since a view and presenter may register an interest in particular events that they expects their model to generate, you cannot plug any view or presenter onto any model and expect them to work. We must classify the "type" of a (model) class by the protocol of messages that it responds to and should also include in this the protocol of events that it triggers. We might call these the in-protocol and out-protocol respectively. It is important, therefore, to only plug a particular view or presenter onto a certain type of model. For example, the ListBox and ListPresenter classes expect a list type of model with an in-protocol and out-protocol as exemplified by the ListModel class.
In general, and not unreasonably, you can only plug together model, view and presenter objects if they have been designed to work together. The bandwidth of communication between each of model, presenter and view (i.e. the size of their in-protocols and out-protocols) is important and is in direct opposition to the flexibility of the elements. The simpler you keep the protocols the more flexible and interchangeable the elements will be. Hence the use of the ValueModel protocol discussed elsewhere..
Each MVP triad can be thought of as a component. As such it can later be reused to build other composite components, which can then be reused to build other components... and so on. In fact, this block of three elements, model, view and presenter is very similar to a component in a more traditional component-based architecture such as ActiveXä or Java Beansä . A component architecture gains ease-of-use by allowing various components to be "plugged together" in fairly simple ways without requiring too much coding to make the composition work together as required.
The Dolphin MVP components also allow this, as you might expect, but have an additional advantage in that each of the elements can be individually replaced by another. This allows you to create a proliferation of new components simply by plugging together different combinations of model, view and presenter elements. The number of possible combinations is limited merely by the expectations of "type" between the elements as discussed above.
With MVP, all of the elements are pluggable. If you wish to change the visible representation of a component then you merely plug a different sort of view onto the presenter. For example, you might choose to plug a "Multiline text" view onto a TextPresenter to place in the default "Single line text".
Try the following examples.
This will create a TextPresenter component and display it connected to a TextEdit view. The view is automatically cradled by a top-level shell window for convenience. As you type, you'll notice that you can't enter any newline characters since a TextEdit only supports single line text.
Tip: By the way, if you're wondering where the default view for a presenter is specified, its in the #defaultView class method. You'll see that TextPresenter class>>defaultView specifies 'Single line text', which is the resource name for an instance of TextEdit that has been saved with the class. You can see, and edit this resource by opening the Resource Browser and double-clicking on 'TextPresenter - Single line text'.
It is also possible to open other views onto a TextPresenter.
TextPresenter show: 'Multiline text'.
As you might imagine, you are now able to enter text into this presenter over multiple lines.
Another experiment, this time using a BooleanPresenter might be:
BooleanPresenter show: 'Push to toggle'.
If you wish to change the way the data is held by a component then the model itself can be replaced or reconfigured. For example, a ListPresenter creates, and connects itself to, a ListModel by default. If you take a look at the ListModel class you'll see that it is basically a wrapper around any sequenceable collection. It is the ListModel's job to marshal requests to the underlying collection so that appropriate change notifications can be triggered.
Tip: the default model for ListPresenter is specified in its #defaultModel class method. You'll see that ListPresenter class>>defaultModel answers a ListModel created on an OrderedCollection.
So, create a standard ListPresenter and fill it.
lp:= ListPresenter show.
lp model addAll: Object methodDictionary keys.
However, it is also possible to create a ListPresenter on a model different from the default:
lp:= ListPresenter showOn: (ListModel with:
lp model addAll: Object methodDictionary keys.
This time all the method selectors in the list appear in sorted order by warrant of the SortedCollection.
So far, we have dealt with situations where there is only ever one view onto any model. It might, therefore, be reasonable to ask why we make views Observers onto their models since this case the overhead of using the triggering mechanism to pass change notifications around. It is obviously more complicated than a direct coupling but there are distinct advantages:
Since we are using an Observer pattern as part of MVP we get the benefit of allowing components to share model data. Normally, since a presenter creates and associates itself with a default model, you might consider that it effectively owns this model data. For most purposes this is acceptable. However, you might want to use two presenters to display/edit the same data and for each to be kept up to date with changes made by the other. To achieve this, both presenters may clip onto a separately created model or one presenter may share its model with another.
In this situation, each view is kept up-to-date with changes made to the model either by the other view or by some other component. To demonstrate this try the following example. It's probably best to execute each line individually and resize the shell windows that appear so they are not overlapping.
m := false asValue.
BooleanPresenter showOn: m.
BooleanPresenter show: 'Push to toggle' on: m.
BooleanPresenter show 'Yes-no text' on: m.
There are two, quite different, types of MVP component that you'll come across. In a Basic MVP Component, a single model connects to a single presenter and a single view. A basic triad such as this can be thought of as an elemental component from which composite components can be built.
An example of a basic component is TextPresenter. Here the default model is a ValueHolder wrapping a String and the default view is a TextEdit.
In a Composite MVP Component, a (usually) complex model is connected to a composite presenter and to a composite view structure. When the model is connected to the composite presenter it is often treated as several sub-models which are individually connected into appropriate sub-presenters and sub-views. The result is that a composite triad inevitably consists of a hierarchy of sub-triads, which are comprised either of basic, or composite triads themselves.
A good example of a composite component is MethodBrowser. The model of the MethodBrowser itself is the singleton instance of SmalltalkSystem and the view is a composite view built using the View Composer. However, the composite contains two additional components. A MethodListPresenter (connected to a ListModel) is used to display the list of methods being browsed the method source text is displayed in a SmalltalkWorkspace presenter.
Tip: In fact, the SmalltalkWorkspace is a rather odd component. For clarity in the diagram above, it has been shown as being connected to a model, which would most likely be a String (to hold its text) wrapped as a ValueModel. However, in reality, a SmalltalkWorkspace does not hold its text in a model at all; merely in its view. This is for efficiency purposes, to save having to hold a copy of a potentially large amount of text in the image. The actual model for a SmalltalkWorkspace is again the SmalltalkSystem singleton.
Actually, the MethodBrowser component is a good example of the flexibility of MVP compared with other component architectures. It is used, fairly obviously, as part of a MethodBrowserShell with a particular view ('Basic method browser') created by the View Composer. It is also used as an embedded component inside a ClassBrowserShell but this time with another completely different view arrangement ('Basic class browser') which isn't even confined to a rectangular area. See the diagram below.
In Dolphin MVP, when you create a new component you will always create a new class of presenter. This is true for both basic presenters and composite presenters. This is because you need to add application functionality by writing Smalltalk code in the new class. We can say, therefore, that MVP presenters are "class based".
Views, however are somewhat different. When you specify a default (or indeed any sort of) view for a presenter you provide the name of a Dolphin resource object that will be loaded to become an instance of the view to which the presenter will be connected. The view instance is initially created with the View Composer and saved down to a resource from there. Hence we can say that MVP views are "resource based".
Adding basic view resources
As we've just seen, and its name suggests, the View Composer is most useful for creating composite views and saving these down as resources. However, you can probably see that the basic views in the View Composer's toolbox (such as 'Single line text' and 'Check box') must have come from somewhere. In fact, elemental view resources such as these are created by sending #makeResource:inClass: to any chosen view class.
Tip: View resources are just view objects that have been streamed onto an STB Filer. They are normally held as ByteArrays in the image but it is possible to export a view resource to a binary file such that it can be loaded from there.
Early Smalltalk development environments used an application framework known as Model-View-Controller or MVC. Although similar to MVP in a number of respects, there are significant differences in Dolphin's framework that make it more suitable for use in conjunction with a modern operating system such as Microsoft Windows. For a further discussion of these differences see Model-View-Controller.