Copyright © 1994, Bobby Woolf and Knowledge Systems Corporation. All Rights Reserved.
How can one object retrieve and change a collaborator object in a uniform way, regardless of what larger object the collaborator may be a part of?
How can two objects share a common value in such a way that both can access and change the common value, and whenever one changes the common object the other will be notified automatically?
How can an object that is using the value contained in a ValueModel be notified whenever the value changes?
What if the update that is performed when a change occurs is so simple that a separate <something>Changed method seems unnecessary?
When using ValueModels, how does the programmer know when to use value and value:?
How can multiple objects that need to share the same value be assured of sharing a single ValueModel?
When an object gets a value from a ValueModel, how does it know the type of object it will receive?
How can I wrap ValueModel behavior around the objects in my instance variables?
How can I wrap ValueModel behavior around my retrieval of an aspect of another model?
My view has a number of values. How can I allow the user to change all of them without changing them in the application model, then accept or cancel all of the changes at one time?
How can I easily convert a real world quantity to a percentage such that whenever the quantity changes, the percentage is automatically recalculated?
How can I wrap ValueModel behavior around a particular element in a Collection?
How can I wrap ValueModel behavior around a particular instance variable in an object without changing what kind of object an instance variable holds?
How can I wrap ValueModel behavior around an arbitrary portion of an object?
Two objects wish to share a value, but one dependent wants it expressed as one type and the other expects another type. How can the value appear as the appropriate type of object for each dependent?
What object should I use to keep track of how much a point's position on a grid has changed?
How can I set-up a list such that it can tell me which item is currently being used?
In Objectworks 4.1, ParcPlace introduced the Value Model framework. Back then, it only consisted of two concrete classes. The Objectworks code didn't use this framework very much, so neither did most application developers. However, this framework was greatly enhanced to help form the foundation of ParcPlace's next release, VisualWorks 1.0. This version of Smalltalk expanded the framework to contain numerous concrete subclasses and associated classes.
This document will answer these questions:
VisualWorks uses this framework to convert users' painted views into application models containing executable code. The framework is used extensively by the generated code in application models and the code developers write to enhance their application models.
However, the framework does more than just enable VisualWorks to automatically generate application model code. It significantly enhances the dependency framework and helps developers separate application models and domain models into distinct objects. In fact, although VisualWorks' code generators use ValueModels exclusively in ApplicationModels, they may be even more useful when employed in domain models.
This paper contains guidelines explaining what ValueModels are, why they were developed, and how to use them. You, the reader, are expected to be familiar with ParcPlace Smalltalk (either Objectworks 4.x or VisualWorks) as well as basic Smalltalk syntax and object-oriented practices. You should have a basic understanding of the dependency framework whereby an object notifies its dependents of a change by sending itself changed..., which causes each dependent to receive update:.... You should have used the UI Painter facility in VisualWorks to create your own subclass of ApplicationModel and design a view for it.
In general, though, intimate knowledge of Smalltalk is not required to understand these guidelines. Although they document the Value Model framework in VisualWorks, the concepts behind the framework that motivate its use are not VisualWorks specific or even Smalltalk specific (although they may be specific to object-oriented programming!). Thus even though these guidelines are designed primarily for VisualWorks programmers, other developers using object-oriented techniques can benefit from them as well.
"Value Model" is the name of a framework in VisualWorks. It is implemented by the ValueModel hierarchy, but also has some related classes outside that hierarchy like SelectionInList and DependencyTransformer. ValueModel itself is an abstract class; you actually use instances of ValueModel's concrete subclasses.
A ValueModel has two main characteristics:
This provides a simple, generic interface between any object (a value) and a value-based object (such as a visual widget that displays a single value). The value and the value-based object do not have to be customized for each other; the ValueModel does this by connecting the two and translating the interactions between them as necessary. Thus the value-based object neither knows nor cares where the value comes from or how to access it. The object is able to simply send value to its ValueModel, which in turn does whatever is necessary to obtain the value, then returns it.
If the value changes, it notifies the widget. This is the case even when multiple widgets share the same value; when one widget changes the value, it does not have to perform any notification because the ValueModel does so. In this way, the widget simply uses the value as it sees necessary and does not have to worry about what the consequences might be to anyone else who might also be using the value.
For additional information about what ValueModels are and how to use them, see [Par93].
Here are some guidelines that describe what ValueModels are and what they do:
How can one object retrieve and change a collaborator object in a uniform way, regardless of what larger object the collaborator may be a part of?
The object will treat its collaborator as a single value. It will need to be able to retrieve this value, and change its setting by storing a new value. Since the value may change, the object will need to be notified when the value changes.
The object will need to send messages to retrieve and set the value. The object's design can be simplified if these messages are always the same. It will also need to listen for notification that the value has changed to a new object. Again, if this notification is always the same, the object's design can be simplified.
The value may be stored in several different ways: It may be a stand-alone object. It may be a part of a larger object. It might not be an object at all, but rather one that can be generated from others whenever it is needed. If these specifics of how the value is stored can be hidden from the object using it, that object's design can be simplified.
Behavior will be needed to allow a value to be accessed using standard messages. More will be needed to access it, depending on how it is stored. This behavior should be implemented in a way that is as reusable as possible. Then the code that handles these issues for one value can be used with all values that have similar circumstances.
Use a ValueModel to store the value. ValueModel is an abstract class, so you'll actually use an instance of one of ValueModel's concrete subclass. Which subclass you'll use will depend on a couple of factors. How is the value stored in the system? How does it needs to be retrieved from the system? What conversion needs to be performed on the object during the retrieval process? (See "3. Types of ValueModels".)
A ValueModel has the aspect of value. This means that to retrieve the value, the getter message is value; to change the value, the setter message is value:; and to listen for updates when the value changes, the update aspect is #value.
Once the ValueModel is established on the value, you don't need to know where it comes from, what its real name or aspect is, or what other objects might be using it. The ValueModel takes care of these details. You automatically know how to retrieve it and change it using the standard value aspect. In this way you always know how to use the value and don't need to be concerned about the details.
Example 1
Let's say you want to implement a time widget that displays a clock face on a view. (Maybe you implement two, analog and digital.) Its state will be based on an instance of Time.
Where might this instance of Time come from? Someone might create one in a workspace and send it displayAsClock. It might be one half of a DateTime object, or an aspect of a LogEvent object. It might sit in a particular place in memory, the result of a primitive that constantly reads the system clock and replaces the Time instance whenever the clock changes. As you can see, you don't know where it might be coming from.
And you shouldn't know where it's coming from. Even if you're implementing the clock view for one particular model, you should generalize your implementation to fit any Time instance that comes along.
So design it to access its Time object via a ValueModel. That means that it uses value to get the Time from its view's model, value: to set it, and re-performs the get when it receives a #value change notification.
Which concrete subclass of ValueModel you use will depend, case by case, on how the Time is stored in the system. With the appropriate kind of ValueModel, your clock widget will work with any model that contains a Time, including a stand-alone instance of Time itself.
Example 2
Most of the widgets that use ValueModels are visual widgets. For an example that is a model widget, see SelectionInList (see also "3.10 Use a SelectionInList to hold a list and its selection").
Whereas a ValueModel has one aspect, value, SelectionInList has three: list, selectionIndex, and selection. The first two are implemented "physically" through the instance variables listHolder and selectionIndexHolder; the third is implemented "logically" in terms of the first two. The two instance variables are, you guessed it, ValueModels (actually ValueHolders; see "3.1 Use a ValueHolder to hold a stand-alone object").
How can two objects share a common value in such a way that both can access and change the common value, and whenever one changes the common object the other will be notified automatically?
The change/update mechanism in VisualWorks enables an object to notify its dependents when its internal state changes. But that mechanism does not work when the object is replaced with an entirely new object; all of the dependents are still attached to the old object. The dependents and the old object are unaware that the old object has been replaced with the new one.
The behavior that is performing the swap could use the message become: to change all pointers to the old object to point to the new one. However, become: is inefficient, makes code difficult to maintain, can cause unwanted side effects, and doesn't notify dependents. It will confuse objects that wanted to point to the old object even if it were replaced with a new one, and the dependents will not be notified that the replacement has been made.
For a set of objects that are sharing a value to all automatically share a replacement value, their pointers require a level of indirection. Instead of pointing directly to the shared object, they should point to an object that will not be replaced, which in turn will point to the shared value. This intermediary object will be a container that holds the value being shared.
Because the objects sharing the value will have to access it through the container, that container will know when the value has been replaced. Thus the container can notify the dependents whenever the value is replaced.
When two objects need to share a common object, they should place that object in a ValueModel and share that ValueModel. They can then register their interests on the value and the ValueModel will notify them when the value changes (see "2.1 Use onChangeSend:to: to register interests on a value"). To manipulate the value, they will go through the ValueModel (see "1.1 Use a ValueModel to generalize an object's aspect"), allowing it to monitor their actions. As necessary, it will execute overhead behavior like translating the value or notifying dependents of a change.
Let's say two application models need to use the same value in a domain model. Either application model might change the value, in which case the other needs to be informed so that it can update itself accordingly.
A simple way to accomplish this is for the domain model to store the value in a ValueModel. Then each application model can latch on to the ValueModel and access the value by sending the model value. Each one can change the value by sending the model value:. Of course, when one changes the value, the other needs to know about it, so both will register their interest with the ValueModel (see "2.1 Use onChangeSend:to: to register interests on a value"). This way, when the value changes, the application models will be notified. In fact, the object changing the value may not be one of the application models. The domain model may decide to change its internal state, and in doing so, change the value. Even in this case, all of the value's dependents (who registered themselves as such), including the two application models, will be notified.
A ValueModel is a powerful mechanism that will abstract an object's aspect and define its dependents. However, given that ValueModels provide this behavior, how should programmers design them into their code to implement useful system functionality?
ValueModels also introduce a layer of indirection that can quickly complicate the model's use of its aspects and obscure the interface protocol for the collaborators that use those aspects. When a programmer first starts to use ValueModels, he can easily become confused about when he needs to send value or value: to an object. Because of this confusion, he will have difficulty getting otherwise simple code to work. Once his code does work, it often contains unneeded senders of value and value:; these can lead to more subtle problems.
This section contains guidelines and tips for how to write code that uses ValueModels. A programmer does not have to follow these guidelines to write code successfully, but they will often make the code simpler and better encapsulated. This will be an especially big help to someone trying to learn how to use ValueModels.
Here are some guidelines that describe how to use ValueModels:
How can an object that is using the value contained in a ValueModel be notified whenever the value changes?
When multiple objects share a value, they should share a single ValueModel wrapped around that value (see "1.2 Use a ValueModel to share a value"). When the ValueModel changes the value, it will notify its dependents.
To be notified, each dependent must register its interest in this value (hence making itself a dependent). This will make it a logical dependent of the value (which is implemented as making it a physical dependent of the ValueModel).
Since the only update aspect a ValueModel ever sends is #value, this is the only one the dependents must listen for.
It is insufficient for a dependent to simply listen for notification of a change. When it receives such notification, it must perform actions to respond to the change. To encapsulate this series of actions and give it a name, they should be collected together into a method (which in turn may use other methods). By collecting this series of actions into a method, the series can be reused whenever it is needed, even if the need is not caused by a change notification. For example, a newly created object might perform these actions to initialize itself, then re-perform them whenever it receives a change notification.
When an object's design dictates that it be a dependent of a value, the code to implement that assumption should be encapsulated inside the object.
When an object is no longer being used, it should release its dependencies so that it will no longer receive notification of changes.
To register your interest in a ValueModel's value, send the ValueModel
aValueModel onChangeSend: aSelector to: aDependent
where aSelector is the name of the method you want run when the value changes and aDependent is the object that contains the method (usually yourself).
The onChangeSend:to: messages should be sent by the initialize method of the dependent object; this means that the to: parameter will be self. By establishing the dependency during initialization, this ensures that it will be established only once, and that it will be established before the object is used.
The dependent object should also implement release to release its dependencies by sending
aValueModel retractInterestsFor: aDependent
to the ValueModel. In this way, for each sender of onChangeSend:to: in initialize, there should be a corresponding sender of retractInterestsFor: in release.
(Note: In practice, sending retractInterestsFor: when the dependent is released is not always necessary. When the ValueModel's container is released and garbage collected at the same time as the dependent is, whether or not their dependency is disconnected is irrelevant. However, in many cases, the dependent is released while the ValueModel's container remains in use. In such cases, to keep the obsolete dependent from receiving updates from the ValueModel, it should release its dependency as part of releasing itself. Thus it's safest to always release the dependency, even though this sometimes is not necessary.)
Example 1
An application model needs to know when the value in the domain model changes. Luckily the domain model stores the value in a ValueModel so that the application model can easily register its interest in the value and receive notification when the value changes. The application model has a method, domainValueChanged, that it wants to run whenever the value in the domain model changes.
Here's the code in the application model to do this:
initialize . . . self domainModel sharedAspectHolder onChangeSend: #domainValueChanged to: self. . . . domainValueChanged "The domain model changed; update the app model" self sharedAspect: self domainModel sharedAspect release . . . self domainModel sharedAspectHolder retractInterestsFor: self. . . .
(In the above examples: domainModel is a method that returns the application model's domain model; sharedAspectHolder returns the ValueModel holding the value of interest (see "2.3 Encapsulate senders of value and value:"); and domainValueChanged performs the steps in the application model necessary when the value changes. In real code, these methods would have more descriptive names.)
Example 2
I have a list of Things being displayed in my view, and I want selectedThingChanged run whenever the selection in the list changes.
Here's the code (see also "3.10 Use a SelectionInList to hold a list and its selection"):
What if the update that is performed when a change occurs is so simple that a separate <something>Changed method seems unnecessary?
If an object is to understand a particular message, it must implement or inherit the corresponding method. Each such method adds to the object's bulk and the amount of code a developer must learn to maintain the object. Thus an object should not contain methods it does not need.
Each time a dependent registers its interest on a value using onChangeSend:to: (see "2.1 Use onChangeSend:to: to register interests on a value"), it must specify a <something>Changed message in itself to be sent. Thus the dependent must implement or inherit a method for this message.
Complex methods are necessary; they are how objects implement their behavior. But simple <something>Changed methods that do nothing more than re-fetch the value that changed do not significantly enhance the overall behavior provided by the object.
An object designed to automatically re-fetch a value whenever it changes would encapsulate this functionality. It would automatically initialize its value in the dependent object, then update it when the value in the parent changes. This behavior would be easy to reuse, and its role would be easily recognizable, simplifying maintenance.
Rather than implementing an extremely simple <something>Changed method in the dependent to be sent by onChangeSend:to:, eliminate that extra step by using a ValueModel instead. This will connect the parent and dependent models using two ValueModels. The parent object will contain the first ValueModel; that ValueModel will be the subject of the second, which will in turn be contained by the dependent object. Whenever the value in the parent changes, the first notifies its dependents, which causes the second to update its value in the dependent.
Example 1
A domain model has an aspect address. The application model needs to hold this address so that it can display it in its view. The domain model should store its address in a ValueHolder (see "3.1 Use a ValueHolder to hold a stand-alone object") so that the application model can register its interest on the value by sending the ValueHolder onChangeSend: #addressChanged to: self (see "1.2 Use a ValueModel to share a value"). addressChanged would simply read the new value from the ValueHolder and store it into the application model: self address: self domainModel address.
However, the addressChanged method is so simple that it's not even necessary. Instead, make a variable in the application model that stores the value in an AspectAdapter (see "3.2 Use an AspectAdapter to hold an aspect of an object"). The AspectAdapter's subject channel is the address ValueModel in the domain model and its aspect is value. This way, whenever the address value in the domain model changes, its ValueHolder will issue an update. This will trigger the AspectAdapter to issue an update, which will cause its dependents (such as the field subview that displays the address) to update. All of this is done as a chain of events without you needing to write any further code (such as addressChanged).
(Note: You could almost have the instance variable in the domain model and the one in the application model contain the same ValueHolder. Unfortunately, this typically doesn't work because VisualWorks feels compelled to insert a TypeConverter (see "3.8 Use a TypeConverter to convert a value between types") between the domain model's ValueHolder and the application model. It does this because the ValueHolder can hold any object but the application model requires a specific type, such as a String or a Number. Thus the TypeConverter is needed to guard against the ValueHolder containing nil, which will be converted to the empty string or zero. Having both models share the same ValueModel might seem to be the simplest solution, since it uses only one ValueModel instead of two. However, VisualWorks will insert one anyway, so you get two ValueModels even if you only specify one. Since an AspectAdaptor is a little more efficient than a TypeConverter, you might as well specify which type of ValueModel to use.)
Example 2
The application model contains a list and a field that should display the current selection in the list. You could use a SelectionInList and send its selectionIndexHolder onChangeSend: #selectionChanged to: self, where selectionChanged reads the selection and stores that in the field's ValueModel (typically a ValueHolder).
There is, however, a simpler way that avoids implementing selectionChanged. Make the field's ValueModel a PluggableAdaptor like this:
field := (PluggableAdaptor on: listSelectionInList) getBlock: [ :m | m selection] putBlock: [ :m :v | m selection: v] updateBlock: [ :m :a :p | a == #selectionIndex].
This way, whenever the selection changes, the PluggableAdaptor will catch the update and update the field with the new value (see "3.7 Use a PluggableAdaptor to hold some part of an object").
(Note: The field should really just use an AspectAdaptor whose aspect is selection (see "3.2 Use an AspectAdapter to hold an aspect of an object"). However, when a SelectionInList's selection changes, it issues the update aspect #selectionIndex, not #selection. Since an AspectAdaptor's update aspect must be the same as its get selector, you have to use a PluggableAdaptor because it allows you to specify them separately. This would no longer be necessary if SelectionInList were fixed to issue both #selection and #selectionIndex update aspects whenever the selection/selectionIndex changed.)
Example 3
Use AspectAdaptors (see "3.2 Use an AspectAdaptor to hold an aspect of an object") on a "selection channel" to allow the selection of an object and display its aspects. In this technique, a ValueModel is set-up to hold the current selection. If this selection is made via a SelectionInList, use the technique described above to attach the ValueModel to the SelectionInList. This ValueModel that holds the selection is called a selection channel. With the selection channel established, attach AspectAdaptors (or other ValueModels) to it to display the object's aspects (or other values).
With this arrangement, whenever the selection changes, the selection channel's value changes, which triggers the AspectAdaptors. They in turn re-read their values and update their dependents, which redisplay or otherwise update themselves.
This selection channel technique is well documented in [Par93].
When using ValueModels, how does the programmer know when to use value and value:?
To use a variable's value, a programmer must know what type of object the value is. This way he knows what protocols the value supports and thus what messages it will understand.
When a message returns a value (besides self), the message name should describe or suggest the value returned.
When using ValueModels, the programmer must know which messages return the ValueModels and which return the values themselves. Usually the sender will be interested in the value, not the ValueModel that contains it.
An object's accessing protocol should simply provide its aspects while hiding how those aspects are implemented. This will better encapsulate the object and make it simpler to maintain.
Implement separate messages for accessing an object's aspects (values) verses accessing the ValueModels that contain those aspects.
If a model has an aspect called aspect that is being stored in a ValueModel that will be held in an instance variable, name the instance variable aspectHolder. Initialize the instance variable in the initialize method to be the necessary kind of ValueModel. Create a getter for it named after the instance variable, aspectHolder, but do not implement a setter for it (see "2.4 Ensure that all objects sharing a value use the same ValueModel"). Also implement a pair of getter/setter methods for the value, aspect and aspect:, that use the aspectHolder method.
Use the message aspectHolder when you need the ValueModel, like to send it onChangeSend:to: (see "2.1 Use onChangeSend:to: to register interests on a value"). Otherwise, use aspect and aspect: to get and set the value; they encapsulate the object that implements them, simplify its accessing protocol, and hide exactly how the value is stored.
Example 1
A model has the aspect address which the model stores in an instance variable. You need to be able to update other values when the address changes, so you'll store the address in a ValueModel and the other values will register their interests on it using onChangeSend:to:. You want to avoid using senders of value and value: in your code.
Name the instance variable addressHolder and initialize it in initialize to be an appropriate kind of ValueModel. Implement addressHolder to return the instance variable's value, but don't implement addressHolder:. Implement address as "^ self addressHolder value" and address: as "self addressHolder value: newAddress".
Now, to get and set the address, use the messages address and address:. To be notified when the address changes, send onChangeSend:to: to the result of the message addressHolder.
Example 2
The model in Example 1 is an ApplicationModel; the aspect address is being set via the Properties Tool or Multi Tool.
Specify the aspect as addressHolder (to get VisualWorks to give the instance variable the name you want). When you install and define the model, it will create the instance variable addressHolder and implement the method addressHolder for you (and not implement addressHolder:, which is fine). Now implement address and address: as directed above.
Example 3
If the instance variable is to hold a SelectionInList (not a ValueModel), don't call it <list>Holder because that's misleading. A variable named <aspect>Holder should understand the value aspect, but a SelectionInList will not. Instead, name the variable <aspect>SelectionInList or <aspect>SIL, which indicates that it understands the aspects list and selection.
How can multiple objects that need to share the same value be assured of sharing a single ValueModel?
When two objects need to share the same object, they should share a single ValueModel wrapped around that object (see "1.2 Use a ValueModel to share a value").
The shared ValueModel will have to be stored in a commonly accessible place; usually, one of the objects will store the ValueModel and make it accessible to the others.
If the ValueModel stored in this common place is shared by a couple of objects, then changed to contain a different ValueModel instance, not all of the objects will be sharing the same ValueModel.
To make sure they are all sharing the same ValueModel instance, the ValueModel should be set once and then never changed.
When an instance variable holds a ValueModel, create a getter method for it but not a setter method, and initialize it in the initialize method. This way, it gets set once and will never get reset.
This guideline will be sufficient for typical uses. There are some fairly advanced uses of ValueModels, however, where a method to set the ValueModel externally is necessary.
See the examples in "2.3 Encapsulate senders of value and value:".
When an object gets a value from a ValueModel, how does it know the type of object it will receive?
ValueModel is a container object. The object it contains, its value, can be any type of Object. ValueModel is implemented to work with any type of Object as its value.
Collaborators using an object interact with it through one or more behavior protocols. Thus the object must be able to perform the protocols. In implementation terms, this means that the object must understand the messages being sent to it, which are the messages in those protocols.
An object's type is defined by the protocols it is able to perform. The more specialized an object's behavior is, the more specialized some of its protocols are. Collaborators will interact with specialized objects using specialized protocols that most objects don't perform.
Although a ValueModel can hold any type of object as its value, the collaborators using that value may use a protocol that not all objects perform. Thus the value in a ValueModel cannot be just any type of object, it must be of a type that supports the protocol that its collaborators will use. If its collaborators use several protocols, the value must be of a type that supports all of them.
When defining a variable's type as ValueModel, also specify the ValueModel's value's type. Declare and initialize the ValueModel with a valid value. When changing the ValueModel's value, ensure that the new value is of the specified type.
When implementing code, you must make sure that the object that is used as an argument to value: is of the specified type. This way, when other objects retrieve the value, they can assume its type is correct and that it will support the protocols they will use.
Let's say a Person domain object contains an age aspect that must be a non-negative integer. age is stored in a ValueHolder to simplify its use.
These code segments represent valid uses of age that will keep its type, Integer, consistent:
These code segments show invalid uses of age because they do not preserve the Integer type specification:
Be careful to write code like the first set, not the second, in order to keep a variable's type consistent.
Typically, discussions about the Value Model framework center around the class ValueModel, because it is the abstract class that defines the basic behavior and interface of all ValueModels. It is, however, an abstract class; there are no instances of ValueModel to use. A programmer must actually use instances of ValueModel's subclasses.
In VisualWorks 1.0, the ValueModel hierarchy contains a number of concrete subclasses. Their front end is always the same: a single aspect named value that remembers the interests registered on that value. What distinguishes them is their back ends: how they attach to other models, how they hold/retrieve their values, and how they convert and/or translate their values. By knowing what each one does, a programmer can choose which one to use for the job at hand.
Here are some guidelines that describe how to use the different types of ValueModels and their associated classes:
How can I wrap ValueModel behavior around the objects in my instance variables?
ValueModel behavior is often desirable (see "1. What is a ValueModel?").
The ValueModel must store the object it is holding because there are no other objects to do so.
The ValueModel does not need to perform any sort of conversion or translation on the value; it should just return the value as is.
Use a ValueHolder, the simplest and most commonly used type of ValueModel. It will wrap the object within itself, thus giving the object ValueModel behavior. Store the ValueHolder in the instance variable.
A domain model has multiple aspects; the value of each of these is stored in an instance variable. Multiple application models may need to share each of these values.
Make each instance variable a ValueHolder to store the aspect's value. This will facilitate abstracting the value's aspect and make it easy to register interest on the value, without introducing any unnecessary overhead.
A simple way to wrap a ValueHolder around a value is to send the value asValue (The message asValueModel would probably be more intuitive.). So to set-up a domain model with a number of aspects in ValueHolders (see "2. How to use ValueModels"), your initialize method will look like this:
initialize . . . aspect1Holder := self aspect1DefaultValue asValue. aspect2Holder := self aspect2DefaultValue asValue. <etc.> . . .
Then it will need getters for the aspect holders and getters and setters for the aspect values.
How can I wrap ValueModel behavior around my retrieval of an aspect of another model?
ValueModel behavior is often desirable (see "1. What is a ValueModel?").
The ValueModel does not need to actually store the value because it is already being stored by another model.
When that model's aspect's value changes (and the model notifies its dependents), the ValueModel should update its value. If the model does not notify its dependents when its aspect's value changes, its dependents (including your ValueModel) won't get updated.
The ValueModel does not need to perform any sort of conversion or translation on the value; it should just return the value as is.
Use an AspectAdaptor, popular because models with simple aspects are popular. Unlike a ValueHolder (see "3.1 Use a ValueHolder to hold a stand-alone object"), the object itself won't be wrapped with ValueModel behavior. But the process of retrieving and storing the object as an aspect of its container model will be wrapped as a ValueModel. This is how this kind of ValueModel is able to monitor the container model for changes in the aspect.
A domain model has multiple aspects; the value of each of these is stored in an instance variable. The developer who implemented the domain model used simple aspects to store and retrieve the values, but he didn't use ValueModels to make registering dependencies easy. Your application model needs to use some of these aspects.
Your application model will need an AspectAdaptor for each aspect it wishes to share with the domain model. Let's say these are the specifics for two of the aspects (these examples are from [Par93]):
aspect name | getter name | setter name | update aspect |
name | name | name: | #name |
address | getAddress | setAddress: | #getAddress |
(Note: The update aspect must always be the same as the getter name. If the aspect you're adapting does not follow this convention, use a PluggableAdaptor; see "3.7 Use a PluggableAdaptor to hold some part of an object".)
The initialize code to set-up these two adapters would be:
initialize nameHolder := (AspectAdapter subject: domainModel sendsUpdates: true) forAspect: #name. addressHolder := (AspectAdapter subject: domainModel sendsUpdates: true) accessWith: #getAddress assignWith: #setAddress.
Then it will need getters for the aspect holders and getters and setters for the aspect values.
My view has a number of values. How can I allow the user to change all of them without changing them in the application model, then accept or cancel all of the changes at one time?
This mechanism should be a ValueModel so that it will have the generic aspect value. That way, it can be used to store any aspect necessary (see "1.1 Use a ValueModel to generalize an object's aspect").
It should get its value from another ValueModel, so that it can retrieve its value generically.
This mechanism will need a trigger with three positions: commit, neutral, and flush. Nothing happens to the values in the application model until the trigger flips out of neutral.
If multiple fields are to trigger simultaneously, their ValueModels will need to share a single trigger.
Use a BufferedValueHolder as a layer of separation between the model and the ValueModel for a field. A regular ValueModel will immediately store a new value in the model (and notify dependents accordingly). However, a ValueModel stored inside a BufferedValueHolder will suspend the new value until you say to commit it to the model.
Let's say an application model has three fields and a pair of accept/cancel buttons. You want to allow the user to edit the fields, but not commit the edits to the model until the user presses accept. If he presses cancel, the edits should be discarded and the fields should display the original values from the model.
To implement this behavior, use two layers of ValueModels instead of just one. The top layer will consist of unbuffered ValueModels -- like ValueHolder, AspectAdapter, PluggableAdaptor, etc. -- depending on how they must retrieve and translate their values. The bottom layer, between the ValueModels and the application model, consists of BufferedValueHolders, one for each ValueModel in the top layer.
Connect the BufferedValueHolders together with a single trigger that is a ValueModel whose value is a Boolean or nil. As long as the trigger is nil, nothing happens. If the trigger changes its state to true, the BufferedValueHolders commit their values; if it changes to false, they discard their values and reset to the original values from the ValueModels. Thus the accept button should set the trigger to true and cancel should set it to false.
One view could conceivably contain multiple groups of BufferedValueHolders, each group sharing its own trigger. This would allow each group to be committed individually.
How can I easily convert a real world quantity to a percentage such that whenever the quantity changes, the percentage is automatically recalculated?
A quantity is often more easily understood by expressing it as a percentage (a number between 0 and 100%).
A percentage quantity can easily be displayed in intuitive ways: a dial, a needle on a gauge, etc. A group of percentage quantities can be displayed together as a bar graph, a pie chart, etc.
When a quantity is confined to a range, it can easily be converted into a percentage quantity.
The object holding a percentage quantity should be a ValueModel so that it can be used to store any aspect necessary and the quantity can be easily shared (see "1. What is a ValueModel?").
The getter and setter value and value: should return a percentage. If the sender wants the original number within a range, it'll access the value directly.
Use a RangeAdaptor, a ValueModel that converts a number in a specified range into a percentage quantity. It sits between the ValueModel holding the value within the range and the model wanting a percentage quantity. The percentage quantity will be expressed as a number between 0 and 1.
Slider visual widgets use a RangeAdaptor to convert a number in a specified range into a set range, 0 to 1. The RangeAdaptor must know the minimum and maximum values in the range, and must know the step size to use when converting the percentage back into a number in the range. Because a RangeAdaptor is a ValueModel, when the number in the range changes, the RangeAdaptor recalculates the percentage and notifies its dependents automatically.
How can I wrap ValueModel behavior around a particular element in a Collection?
ValueModel behavior is often desirable (see "1. What is a ValueModel?").
The ValueModel does not need to actually store the value because it is already being stored by the Collection.
To hide the fact that this object is stored in a Collection, use a ValueModel so that it will have the generic aspect value.
The ValueModel does not need to perform any sort of conversion or translation on the value; it should just return the value as is.
Use an IndexedAdaptor to give a Collection element the aspect value. Since an IndexedAdaptor interfaces with the Collection via the element's index, the Collection must understand at: and at:put: (thus it must be an Array or an OrderedCollection). The IndexedAdaptor will also allow the element to be shared by multiple dependents. If an IndexedAdaptor changes an element to another, the new element will appear in the old element's position in the Collection.
Let's say you have a list of Person elements and a view that displays the fields for a Person. The fields should all be AspectAdapters on a selection channel. Since the list is a SequenceableCollection, the user can walk through the list by incrementing an index pointer into the list. Thus the selection channel should be a IndexedAdaptor that is a ValueModel on an element in the list.
Here's how to initialize the IndexedAdaptor:
selectionChannel := (IndexedAdaptor subject: personList) forIndex: 1.
The view will initially display the information for the first Person in the list. To display Person n, set the IndexedAdaptor's index to n. To walk through the list, increment or decrement the index.
How can I wrap ValueModel behavior around a particular instance variable in an object without changing what kind of object an instance variable holds?
ValueModel behavior is often desirable (see "1. What is a ValueModel?").
If the nature of the instance variable can be changed from a variable that holds a value to a variable that holds a ValueModel that holds a value, use a ValueHolder (see "3.1 Use a ValueHolder to hold a stand-alone object"). This will require changing some of the object's methods, such as the getter and setter for the instance variable, as well as any other methods that access the variable directly, such as initialize.
If the instance variable is an aspect of the object, with getter and setter methods to access the instance variable, use an AspectAdapter (see "3.2 Use an AspectAdapter to hold an aspect of an object").
If there are other means in the object's public interface to get and set the value of this instance variable, use a PluggableAdaptor (see "3.7 Use a PluggableAdaptor to hold some part of an object").
The ValueModel does not need to actually store the value because it is already being stored by another object.
To hide the fact that this object is an instance variable with no accessor methods, use a ValueModel so that it will have the generic aspect value.
The ValueModel does not need to perform any sort of conversion or translation on the value; it should just return the value as is.
Use a SlotAdaptor to give an object's instance variable the aspect value. The SlotAdaptor will also allow the element to be shared by multiple dependents. If the SlotAdaptor is used to change the value, the object's instance variable will now have the new value.
(Note: Changing an instance variable out from under an object in this manner is a bad idea. You should use a setter method in the object's public interface to make such changes. Changing an instance variable directly is a private procedure that could make the object's state (or those of its dependents) inconsistent. Whenever possible, use an AspectAdapter or a PluggableAdaptor, not a SlotAdaptor. )
Let's say aThing contains the instance variable amount, but aThing does not provide sufficient behavior for getting and setting amount. You can attach a SlotAdaptor directly to the instance variable with this code:
amountHolder := SlotAdaptor subject: aThing. amountHolder forIndex: (amountHolder subject class allInstVarNames indexOf: 'amount').
amountHolder is now a ValueModel whose value is the exact same object as the one in the instance variable amount. If the SlotAdaptor puts a different object in that instance variable, aThing will never know. The next time aThing uses that instance variable, the variable will contain a different value than the last time aThing used it.
How can I wrap ValueModel behavior around an arbitrary portion of an object?
ValueModel behavior is often desirable (see "1. What is a ValueModel?").
The ValueModel does not need to actually store the value because it is already being stored by another object.
If the value is stored in the model as an aspect, use an AspectAdapter (see "3.2 Use an AspectAdapter to hold an aspect of an object").
If the value is an element in a Collection, use an IndexedAdaptor (see "3.5 Use an IndexedAdaptor to hold a single index in a Collection").
If the value is stored in an instance variable that cannot be accessed adequately through the model's behavior, use a SlotAdaptor (see "3.6 Use a SlotAdaptor to hold a single instance variable").
Obtaining the value from the model may be tricky. The value may need to be derived from explicit values in the model; to store it back, the ValueModel may need to dissect the value to get its components and store them into the model. The ValueModel may need to make a series of decisions to decide if its value should be updated.
In fact, the ValueModel may not appear to hold a value at all, but rather to be a detection system for an event that may occur.
Use a PluggableAdaptor to hold an arbitrary portion of an object when no other kind of ValueModel will work. PluggableAdaptor is a very brute force style of ValueModel that is overkill in many cases. However, when the value that one model wants and that which the other model has are very different, a PluggableAdaptor is often able to bridge these differences whereas no other kind of ValueModel can.
Example 1
Let's say the domain model holds the aspects firstName and lastName. The application model wants an instance of FullName that contains the information in the domain model. To store a new name, the FullName will have to be broken apart into the domain model's aspects. When either of these aspects of the domain model changes, the FullName in the application model will have to be updated accordingly.
(PluggableAdaptor on: self domianModel) getBlock: [ :model | FullName firstName: model firstName lastName: model lastName] putBlock: [ :model :value "aFullName" | model firstName: value firstName; lastName: value lastName] updateBlock: [ :model :aspect :parameter | aspect == #firstName | aspect == #lastName].
Example 2
ParcPlace has already implemented some specialized messages; browse PluggableAdaptor to review them:
Two objects wish to share a value, but one dependent wants it expressed as one type and the other expects another type. How can the value appear as the appropriate type of object for each dependent?
When two dependents want two types of objects for the same value, this assumes that the value can be converted from one type of object to the other and back again. If not, they cannot share the same value instance.
The value to be converted should be accessed via the generic aspect value.
The converted value should be accessed via the generic aspect value.
Use a TypeConverter to convert the type of object being held as a value in one ValueModel into the type expected by the model. When storing a new value, the TypeConverter will perform the inverse conversion.
Note: The updateBlock for a TypeConverter is always true. TypeConverter redefines value: not to send out updates. This way, the objects at either end of the converter handle sending-out updates and the converter just conveys them transparently.
Example 1
Let's say a couple of objects want to share a value that is a Number, and a couple of other objects want to share the same value as a String. Here's how:
valueAsNumberHolder := ValueHolder on: theNumber. converter := (TypeConverter on: valueAsNumberHolder)numberToText. valueAsStringHolder := (AspectAdapter subject: converter sendsUpdates: true) forAspect: #value.
With this setup, objects that want to share the value as a Number register their interests on valueAsNumberHolder; those which want the value as a String register with valueAsStringHolder.
Example 2
Many of the most common conversions have already been defined for you, like numberToText. Browse TypeConverter to see others like dateToText, stringOrSymbolToText, and stringToNumber.
What object should I use to keep track of how much a point's position on a grid has changed?
A single Number will be insufficient. It can keep track of the point's distance from the grid's origin, but that will not be enough information to calculate the point's unique position on the grid. There will need to be one Number for each dimension of the grid.
Just knowing the position on the grid (the point) is insufficient. If the point must move across the grid in quantum steps, the size of the minimum step must be available. When the point tries to move, its movements must be constrained to those which are a multiple of the grid step size.
Use a ScrollValueHolder to hold the scroll position of a point (which may or may not be an instance of Point, which is two-dimensional). Set the grid to be the minimum size of each step the point must take when it moves.
ScrollWrappers are the heart of how the scroll bars on a view work; they connect the scroll bars to the view. The ScrollWrapper must remember what its position is; the position is stored in a ScrollValueHolder. The view's scroll position without a valid scroll position being stored in the ScrollValueHolder.
Before storing a new scroll position, the ScrollValueHolder will round it off to the nearest grid position. This position is incremented/decremented by one grid size when a scroll bar button is pushed. The position will be adjusted, if necessary, to make it stay within the wrapper's bounds.
The value of the ScrollValueHolder can be adjusted programatically.
To auto-scroll the view for the user, store a new scroll position into
the ScrollValueHolder. The new value will be validated, stored,
and the wrapper will be informed of the new value, at which time the wrapper
will scroll the view.
How can I set-up a list such that it can tell me which item is currently being used?
A Collection knows which items it contains, and can allow you to reference one item at a time, but cannot tell you which one is currently being referenced.
An item cannot tell you which Collection it is a part of. In fact, it may not be part of any Collection, or may be part of several.
Thus another object will need to contain the list and keep track of which item is currently being used. It should work with any list and selection, regardless of what their aspects are.
You may need to know when the currently referenced item changes.
You may need to know when the items in the list change.
Use a SelectionInList to track a list and its current selection. If you register your interest in one of its aspects (list or selectionIndex), it will notify you of changes.
(Editorial comment: SelectionInList's interface actually isn't as clean as it ought to be. It implements two aspects, list and selectionIndex, using two ValueModels, listHolder and selectionIndexHolder. It then implements a third aspect, selection, but does not implement it using a ValueModel. So there is no selectionHolder to register interest with, nor to send out selection changed aspects. See also "2.2 Use ValueModel chains instead of onChangeSend:to:".)
Here's how to create a SelectionInList of Person objects:
personSelectionInList := (SelectionInList with: self peopleList) selectionIndex: 1.
This will contain the list of Persons; the first one is selected. To be notified when the list is replaced with another, register your interest with listHolder. Register interest with selectionIndexHolder and you'll be notified when the selection (actually the selection index) changes. To change its list, selection index, or selection programatically, use list:, selectionIndex:, and selection:.
Bobby Woolf is a Senior Member of Development Staff at Knowledge Systems
Corp. in Cary, North Carolina. He mentors Smalltalk developers in the use
of VisualWorks, ENVY, and design patterns. He welcomes your comments at
woolf@acm.org or at http://www.ksccary.com.