The Question Mark - blog by Mark Volkmann

Dependency Mechanism

Overview

Any object in Smalltalk can depend on another object and be notified when by it when something significant occurs. For example, multiple objects may want to be notified when a particular property in some object is modified.

This is supported by the “dependency mechanism” which is implemented by a set of methods in the Object class.

Each object maintains a collection of other objects that depend on it.

Messages

To add a dependent to an object, send it the message #addDependent: with an argument that is the dependent object to be added.

To query an object for a collection of its dependents, send it the message #dependents.

To remove a dependent from an object, send it the message #removeDependent: with an argument that is the dependent object to be removed.

To signal that a “change” has occurred in an object and trigger each of its dependents to be notified, set it the message #changed, #changed: or #changed:with:.

The changed method only indicates that some unspecified change occurred. This method is implemented as self changed: self, which calls the method described below.

The changed: method takes an argument named aspectSymbol that specifies the kind of change that occurred. Typically the argument is a symbol. In Cuis Smalltalk this method is implemented as self triggerEvent: #changed: with: aspectSymbol.

The changed:with: method takes an argument that specifies the kind of change that occurred and an object describes the change. Cuis Smalltalk does not support this.

Each of the “changed” methods causes the message #update: to be sent to each of the dependents.

Example

Let’s walk though a simple example. First we will define a Temperature class that maintains a Celsius temperature and notifies dependents when the temperature changes.

Object subclass: #Temperature
    instanceVariableNames: 'celsius'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Demo'

initialize
    celsius := 0

celsius: aNumber
    celsius := aNumber.
    self changed: aNumber.

celsius
    ^ celsius

Next we will define a Water class that maintains the state of the water (#ice, #liquid, or #gas) depending on the temperature. We could define many classes whose instances depend on the temperature. The update: method is responsible for updating the state when it is informed of a temperature change.

Object subclass: #Water
    instanceVariableNames: 'state'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Demo'

initialize
    state := #liquid

state
    ^ state

update: temperature
    state := true caseOf: {
        [temperature <= 0] -> [#ice].
        [temperature >= 100] -> [#gas]
    } otherwise: [#liquid]

Finally, here is some example code that demonstrates using the Temperature and Water classes.

t := Temperature new.
w := Water new.
w state print. "#liquid"
t addDependent: w.
t celsius: -10.
w state. "#ice"