Contents

Class Model


Inheritance:

Object  »
  Model

Class Model is the preferred superclass of classes that implement the functionality of an application. Such classes are commonly called information models. An information model is an object on which user-interface objects such as text windows depend for their data - thus, the interface objects are said to be dependents of the model. A model is expected to notify its dependents about any change that may require an action from the side of the dependents. Change notification is implemented in the instance protocol of Object, but Model reimplements the administration of its dependent objects.

A model is part of the famous model-view-controller pattern, that was first implemented in Smalltalk and became a design pattern ten years before that term was coined. The key idea of that pattern is that a model should not know the elements of its view by reference. A model does not directly communicate with its view. It communicates changes of its state to a collection of not directly referenced receivers. This allows a very loose coupling between a model and its view. Thanks to that loose coupling a model can be used with different views; it can even act without a view!

It should be noted that the possibility to use a model with different views is infrequently exploited. It should also be mentioned, that the model-view-controller metaphor is not available in some newer implementations of Smalltalk. Seemingly many programmers feel better when they are allowed to directly address window elements by means of references. This is also the approach that is chosen in most interface builders.

As soon as a view is given a model, it registers itself as a dependent of that model. In case the view is later given a different model, its disconnects its dependency relation with its old model and registers itself as a dependent of its new model. This mechanism is implemented in View>>model:controller:.

Instance Variables:

Important Instance Methods:

The instance protocol of Model includes a default method to support scrolling of a PluggableListView with the up and down arrows. That method is arrowKey:from:. To use it, you have to provide the message selector as the argument that follows the keyStroke: keyword of the instance creation method of PluggableListView.

Some subclasses of Model are designed for use as components of other models, these are ValueHolder, StringHolder, Switch, Button and OneOnSwitch.

Implementation notes:

The protocol that is used to add and to remove dependencies from a model looks very simple but it contains a subtlety that is worth being explained:

The notification of changes (which is implemented in the instance protocol of Object) iterates over the collection of all dependents. Here is how it looks like:

changed: aParameter
    "  Receiver changed. The change is denoted by the argument aParameter.
       Usually the argument is a Symbol that is part of the dependent's change
       protocol. Inform all of the dependents. "

    self dependents do: [:aDependent | aDependent update: aParameter]

It may happen that a receiver of a change notification decides to remove itself from the collection of the dependents or to add new dependent objects. It is however not allowed to modify the collection of dependents that is currently iterated because this may disturb the iteration. Especially the removal of a dependent may have the peculiar consequence that another dependent will be skipped by the iterator and thus not be notified about the change.

To avoid the loss of notification receivers, the removal of a dependent requires the creation of an entirely new instance. This is done in Object>>removeDependent:

removeDependent: anObject
   "Remove the given object as one of the receiver's dependents."
    | newCollection |
   newCollection := self dependents reject: [:each | each == anObject].
   self myDependents: (newCollection isEmpty ifFalse: [newCollection]).
   ^ anObject

This is a safe solution because it leaves the old collection unchanged. If the old collection is currently the receiver of the #do: message, the execution of that message can be safely continued, otherwise, the old collection becomes inaccessible and will eventually be garbage collected.

Similarly, the addDependent: method creates a new copy of the instance variable dependents. An newly added dependent will thus not receive a change message that was sent before the dependent was added.

As an alternative, it would have been possible to create a copy of the collection of dependents each time a change is notified. It would then be possible to modify the collection of dependents without loosing receivers. Nevertheless that alternative solution is bad because it would copy the collection of dependents very often. Notification of changes is a frequent activity whereas additions to and removals from the collection of dependents are rare. It is therefore much better to copy the collection of dependents only when an addition or a removal takes place.

Using a Weak Array to Store the Dependent Objects

Dependent objects were not always stored in a weak array. (The class DependentsArray was added with change set #3701 into Squeak 3.1) The use of a weak array is motivated by the wish to automatically remove observers that are not referenced by any reachable object. Regrettably however, the use of a weak array to store model observers makes some important uses of observers impossible.

Copying a Model

Models are infrequently copied, but when you copy a model you have to think about dependencies. In Squeak, the method copy answers a shallow copy of the model that shares its dependents with the copy template. For value holders, one would often prefer a copy without the dependents of the template. Such a copy can be obtained with:

component copy breakDependents.

In some Smalltalks, Model redefines the method postCopy to remove the dependents from the shallow copy:

postCopy
   "sent by copy to a just created shallow copy.
    Here we delete the copied dependents from
    the shallow copy."
 self breakDependents

This is perhaps the best solution and should be added to Squeak


Contents