16.1 Implementing Observer in ActionScript 2.0

 <  Day Day Up  >  

The responsibilities of the subject class in the Observer pattern are as follows :

  • Maintain a list of dependent observers

  • Provide methods for adding (registering) and removing (unregistering) observer objects

  • Send notifications to observers

To create the subject class, we'll follow Java's implementation and define a base class, Observable , that any would-be subject class can extend. The Observable class provides the basic services required of all subject classes. Specifically, Observable defines the following properties and methods:


observers

An instance property that holds an array of observers


addObserver( )

A method for adding a new observer to the observers array


removeObserver( )

A method for removing an observer from the observers array


notifyObservers( )

A method for sending change notifications to observers

Now let's turn to the observer classes. The sole responsibility of all observer classes is to provide a standard mechanism for receiving updates.

Certainly, an observer class can (and typically does) implement other behaviors according to a particular situation. For example, our earlier TextReport class implements methods that can display text on screen. However, that extra behavior is not a requirement of the Observer pattern. In the Observer pattern, the sole requirement of an observer class is the ability to receive updates.

In order to guarantee that every observer class provides a standard update mechanism, we'll create an Observer interface that defines an update( ) method. Classes that wish to register with a subject (using addObserver( ) ) must implement the Observer interface and define an update( ) method. When the subject changes state (i.e., has a message to broadcast), it calls notifyObservers( ) , which invokes update( ) on all observers. Each observer's update( ) method responds to the change as it sees fit.

If you're thinking ahead, you may already be wondering how the observers know what changed in the subject. For example, in our hypothetical weather application, the subject, WeatherReporter , sends an update whenever the weather report changes. How do the observers ( TextReport and GraphicReport ) obtain that updated weather information? There are two options, known as the push and pull models.

In the push model, the updated information is pushed to the observers via an info object . The info object is sent to observers by the subject's notifyObservers( ) method. Each observer receives the info object as an argument to its update( ) method. For example, if the WeatherReporter class were to use the push model, it would report weather changes by invoking update( ) on TextReport and GraphicReport , as usual, but would also pass update( ) an object with, say, temperature and precipitation properties. The TextReport and GraphicReport classes would process the object in their respective update( ) methods.

In the pull model, the observers are expected to retrieve the information they want via getter methods defined by the subject. For example, if the WeatherReporter class were to use the pull model, it might define methods such as getTemperature( ) and getPrecipitation( ) . The TextReport and GraphicReport classes would then be responsible for implementing an update( ) method that used getTemperature( ) and getPrecipitation( ) to retrieve the latest weather information.

Both the push and pull models have their pros and cons. The pull model requires observers to manually determine what has changed every time an update is received, which can be inefficient. In the push model, observers are told explicitly what changed, but that requires the subject to know the needs of its observers. The more the subject must know about its observers, the harder it is to modify observers without affecting the subject.

Our implementation of the Observer pattern supports both push and pull models. That is, the basic facilities required to send an info object at notification time are available but optional.

Figure 16-1Figure 16-1 shows the architecture of our Observer implementation. The Observable class is the base class for all subjects. Subject classes (such as WeatherReporter ) extend Observable . Observer classes (such as TextReport ) implement the Observer interface and use addObserver( ) to register with the subject. When a change in the subject occurs, the subject class invokes update( ) on all Observer instances in its observers array.

Example 16-1 shows the source code for the Observable class, which we've chosen to place in the util package (following Java's lead). In addition to the features we've discussed, the Observable class in Example 16-1 provides several other conveniences :

  • The countObservers( ) method, which returns the number of observers in the observers array.

  • The clearObservers( ) method, used to remove all observers with a single, crushing blow.

  • A Boolean property, changed , and the following supporting methods setChanged( ) , clearChanged( ) , and hasChanged( ) . The changed property is used to specify and check whether the subject has changed since the last notification. We use changed when, for the sake of efficiency, we want to broadcast a single notification for multiple state changes in the subject (for an example, see the ClockModel class in Chapter 18).

Figure 16-2. Observer pattern implementation
figs/as2e_1602.gif

For an explanation of the Observable class's source code, consult the comments in Example 16-1. Note, however, that you don't need to know Observable 's internals very deeply. The Observable class is a reusable utility that never changes. We treat it as a black box that we simply extend when implementing the Observer pattern.

Example 16-1. The Observable class
 import util.Observer; /**  * A Java-style   Observable   class used to represent the "subject"  * of the   Observer   design pattern. Observers must implement the   Observer   * interface and register to observe the subject via   addObserver( )   .  */ class util.Observable {   // A flag indicating whether this object has changed.   private var changed:Boolean = false;   // A list of observers.   private var observers:Array;   /**    * Constructor function.    */   public function Observable ( ) {     observers = new Array( );   }   /**    * Adds an observer to the list of observers.    * @param   o   The observer to be added.    */   public function addObserver(o:Observer):Boolean {     // Can't add a null observer.     if (o == null) {       return false;     }     // Don't add an observer more than once.     for (var i:Number = 0; i < observers.length; i++) {       if (observers[i] == o) {         // The observer is already observing, so quit.         return false;       }     }     // Put the observer into the list.     observers.push(o);     return true;   }   /**    * Removes an observer from the list of observers.    *    * @param   o   The observer to remove.    */   public function removeObserver(o:Observer):Boolean {     // Find and remove the observer.     var len:Number = observers.length;     for (var i:Number = 0; i < len; i++) {       if (observers[i] == o) {         observers.splice(i, 1);         return true;       }     }     return false;   }   /**    * Tell all observers that the subject has changed.    *    * @param   infoObj   An object containing arbitrary data     *                    to pass to observers.    */   public function notifyObservers(infoObj:Object):Void {     // Use a null info object if none is supplied.     if (infoObj == undefined) {       infoObj = null;     }     // If the subject hasn't changed, don't bother notifying observers.     if (!changed) {       return;     }     // Make a copy of the observers array. We do this to ensure      // the list doesn't change while we're processing it.     var observersSnapshot:Array = observers.slice(0);     // This change has been processed, so unset the   changed   flag.     clearChanged( );     // Invoke   update( )   on all observers. Count backward because       // it's faster, and order doesn't matter in this case.     for (var i:Number = observersSnapshot.length-1; i >= 0; i--) {       observersSnapshot[i].update(this, infoObj);     }   }   /**    * Removes all observers from the observer list.    */   public function clearObservers( ):Void {     observers = new Array( );   }   /**    * Indicates that the subject has changed.    */   private function setChanged( ):Void {     changed = true;   }   /**    * Indicates that the subject has either not changed or    * has notified its observers of the most recent change.    */   private function clearChanged( ):Void {     changed = false;   }   /**    * Checks if the subject has changed.    *    * @return   true if the subject has changed,     *           as determined by   setChanged( )   .    */   public function hasChanged( ):Boolean {     return changed;   }   /**    * Returns the number of observers in the observer list.    *    * @return   An integer: the number of observers for this subject.    */   public function countObservers( ):Number {     return observers.length;   } } 

Example 16-2 shows the source code for the Observer interface, which resides in the util package along with Observable . The Observer interface is simple, containing only one method: update( ) . We use Observer to guarantee that any class that registers for updates from a subject implements the standard update( ) method. Notice that the update( ) method defines two parameters, o and infoObj . The parameter o contains a reference to the subject that changed. It is used to distinguish changes in one subject from changes in another, as well as to access changed data in the subject (if the subject is using the pull model to update its observers). The parameter infoObj receives the optional info object provided by the subject if the subject is using the push model to broadcast changes to its observers. Note that although a single observer can, in theory, register with multiple subjects (and our code accommodates such a circumstance), in practice, a single observer typically registers with only one subject. An observer registered with multiple subjects must use awkward conditionals ( if/else or switch statements) to distinguish between those subjects at update time. When a program reaches that level of complexity, it's often better to implement full-scale event handling, as described in Chapter 19.

Example 16-2. The Observer interface
 import util.Observable; /**  * The interface that must be implemented by all observers of an  *   Observable   object.  */ interface util.Observer {   /**    * Invoked automatically by an observed object when it changes.    *     * @param   o   The observed object (an instance of   Observable   ).    * @param   infoObj   An arbitrary data object sent by     *                    the observed object.    */   public function update(o:Observable, infoObj:Object):Void; } 

 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net