AspectAdaptors and BufferedValueHolders - what are they?
I used to teach the ParcPlace Introduction to Smalltalk Class. Years later, I remember what used to happen on Wednesday when AspectAdaptors came up - a collective huh???, followed quickly by a blank, glazed-over-eyes stare. The confusion hasn't eased over the years; I still see many questions on this, even from experienced Smalltalkers. So what are they, what are useful for, why do you want them?
Well, consider a simple VisualWorks application - a class Counter with one instance variable - count. Here are some methods:
count: aNumber count := aNumber. count ^count. add: aNumber self count: self count + aNumber.
All pretty simple, right? Now let's have a UI on that - a simple UI that shows us the current value of the count, and gives us a button - each time we press the button, we send #count: to the Counter instance with an argument of 1. easy to enough to string together, but how do we get the UI to be aware of the changed value in the count variable, and display the new value? Well, there are two routes to follow - the MVC dependency model, and the new trigger event system. Here I'm going to use the MVC model - I'll post an alternative using events in the future.
Ok, so the first thing is to change the #count: method as follows:
count: aNumber count := count + aNumber. self changed: #count with: aNumber.
Ok, that signals a change in the Counter object - but how does the UI pick it up? Well, one (complex) way would be to make the UI a dependent of the Counter object, and then implement an #update:with:from: method. This method might look like this:
update: anAspect with: aValue from: aModel anAspect = #count ifTrue: [self counter value: aValue].
There's an obvious problem with this - as the number of objects of interest (in the domain and in the UI) grows, the #update method grows into a complex case statement. There's got to be a better way, right? It turns out that there is - AspectAdaptor. This object does exactly what the name sounds like - it adapts messages that are sent by the domain into ones that are understood by the UI. If you used the VW GUI builder, and your UI class has an input field for displaying counter, then you have a method that looks like this:
counter "This method was generated by UIDefiner. Any edits made here may be lost whenever methods are automatically defined. The initialization provided below may have been preempted by an initialize method." ^counter isNil ifTrue: [counter := 0 asValue] ifFalse: [counter].
That's defining a ValueHolder - a wrapper around a number in this case. We want to replace that with an AspectAdaptor. Here's what that looks like:
counter "This method was generated by UIDefiner. Any edits made here may be lost whenever methods are automatically defined. The initialization provided below may have been preempted by an initialize method." ^counter isNil ifTrue: [counter := (AspectAdaptor forAspect: #counter) subjectChannel: self model; subjectSendsUpdates: true] ifFalse: [counter]
That assumes that you have a line like this in #initialize:
model := Counter new asValue.
So what does the adaptor do? It sets up the communication channel for dependency updates for you automatically, on a per-object basis. The adaptor is now a dependent of the Counter object, and is specifically looking for the #counter aspect when updates come through. Note that the aspect is set to #counter - it's entirely possible to have the get/set messages be different. But AspectAdaptors are more than one-way conduits from the domain up - they are also conduits from the UI down. If you allow user input, a change in the UI will be pushed to the domain. Have a look at the class comments for AspectAdaptor and ProtocolAdaptor (its superclass) to see what's going on.
What if you don't want changes going down as each field on a form is changed though - what if you want a form level update? Have a look at BufferedValueHolder. You can use that to control when updates flow down, via a true/false switch.
This is all documented in the GUI developers guide as well - you should read that over for a more in-depth explanation. If you have questions, send them to me
