16.2 Logger: A Complete Observer Example

 <  Day Day Up  >  

Now that we've implemented the core of the Observer pattern, let's put it to use in a real-world example ”an application log. To create the log, we'll define four classes: Logger , LogMessage , OutputPanelView , and TextFieldView , all of which reside in the logger package. The Logger class is our subject. The OutputPanelView and TextFieldView classes are our observers. The LogMessage class is our info object.

Any class in the application can send a message to the Logger class. The receipt of a new message constitutes a change in the Logger class's state, causing it to broadcast the change (i.e., the message) to all Logger observers. In our case, the Logger observers are OutputPanelView and TextFieldView . The OutputPanelView renders log messages to the Output panel in Flash's Test Movie mode. The TextFieldView renders log messages to a text field on the movie's Stage, allowing them to be seen at runtime in the Flash Player.

Our Logger class uses the push model to broadcast messages. When a message arrives, Logger creates a LogMessage instance and passes that instance on to its list of observers. Each LogMessage provides methods for retrieving the text and severity of the message logged. LogMessage instances can have one of five severities, represented by the integers 0 through 4, as follows : FATAL (0), ERROR (1), WARN (2), INFO (3), and DEBUG (4). The severity-level integers are stored in static properties of the Logger class. They can be converted to human-readable strings via the Logger.getLevelDesc( ) method.

The Logger class provides a filter for suppressing log messages. Using Logger.setLevel( ) , a Logger instance can set a severity level, which determines whether messages should be broadcast or discarded. Messages with a severity level greater than the Logger 's severity level are not broadcast to observers. For example, if a message has a severity of 4 (DEBUG), but the Logger 's severity level is 3 (INFO), the message is not broadcast. The Logger class's filter lets us easily change the quantity and granularity of messages of a log from a central location. During development, we might use a log severity level of 4 (broadcast all messages), but in the final application, we might use a log severity level of 1 (broadcast ERROR and FATAL messages only).

16.2.1 The LogMessage Class

Let's start our examination of our log's source code with the LogMessage class, a simple class for setting and retrieving the text and severity of a logged message. The LogMessage class defines the following members :


msg

An instance property that stores the text of the message


level

An instance property that stores the message severity level


setLevel( )

A method for setting the message severity level


getLevel( )

A method for retrieving the message severity level


setMessage( )

A method for setting the message text


getMessage( )

A method for retrieving the message text

As we'll see later, the Logger class creates LogMessage instances when broadcasting a change notification to its observers. Example 16-3 shows the source code for the LogMessage class.

Example 16-3. The LogMessage class
 /**  * A log message. Sent by the   Logger   instance to all registered  * log observers when a new log message is generated.  */ class logger.LogMessage {   // The text of the message sent to the log.   private var msg:String;   // The severity level of this message.   private var level:Number;   /**    * LogMessage Constructor    */   public function LogMessage (m:String, lev:Number) {     setMessage(m);     setLevel(lev);   }   /**    * Sets the log message.    */   public function setMessage (m:String):Void {     msg = m;   }   /**    * Returns the log message.    */   public function getMessage ( ):String {     return msg;   }   /**    * Sets the severity level for this message.    */   public function setLevel (lev:Number):Void {     level = lev;   }   /**    * Returns the severity level for this message.    */   public function getLevel ( ):Number {     return level;   } } 

Now let's move on to OutputPanelView , a class that receives LogMessage instances from Logger and generates corresponding on-screen messages.

16.2.2 The OutputPanelView Class

The OutputPanelView class displays log messages in the Output panel in Flash's Test Movie mode. It implements the Observer interface from Example 16-2 and defines only one property and one method:


log

An instance property that stores a reference to the Logger instance being observed


update( )

The all-important method used by Logger to broadcast messages to the OutputPanelView instance

By convention, all observer classes should store an instance of the subject they are observing. They use that reference to pull changes from the subject or to set the subject's state. In our case, the Logger class broadcasts its updates using the push model, so the log property in OutputPanelView is not actually used. However, we maintain it as a matter of good form and for the sake of possible future updates to the OutputPanelView class.

Example 16-4 shows the source code for the OutputPanelView class. Pay special attention to the update( ) method, which receives a LogMessage instance as an argument and uses it to display log messages in the Output panel. Notice that the generic infoObj instance received by update( ) is cast to the LogMessage datatype before it is used so that the compiler can perform type checking on it. (Technically, the cast is not necessary in this case because the Object class is dynamic, so invoking non- Object methods on infoObj would not cause an error.)

Example 16-4. The OutputPanelView class
 import util.Observer; import util.Observable; import logger.Logger; import logger.LogMessage; /**  * An observer of the   Logger   class. When a movie is played in   * the Flash authoring tool's Test Movie mode, this class displays  * log messages in the Output panel.  */ class logger.OutputPanelView implements Observer {   // The log (subject) that this object is observing.   private var log:Logger;   /**    * Constructor    */   public function OutputPanelView (l:Logger) {     log = l;   }   /**    * Invoked when the log changes. For details, see the     *   Observer   interface.    */   public function update (o:Observable, infoObj:Object):Void {     // Cast   infoObj   to a   LogMessage   instance for type checking.     var logMsg:LogMessage = LogMessage(infoObj);     trace(Logger.getLevelDesc(logMsg.getLevel( )) + ": " +                                logMsg.getMessage( ));   }   public function destroy ( ):Void {     log.removeObserver(this);   } } 

Now let's look at our log's other observer class, TextFieldView .

16.2.3 The TextFieldView Class

The TextFieldView class displays log messages in a text field in the Flash movie rather than in the Output panel.

The basic structure of TextFieldView is identical to OutputPanelView . Like OutputPanelView , TextFieldView defines a log property and an update( ) method. It also adds a new method, makeTextField( ) , which creates an on-screen TextField instance in which to display messages. And it adds a new property, out , which stores a reference to the text field created by makeTextField( ) . The TextFieldView constructor defines seven parameters:


l

A reference to the Logger instance that will be observed


target

The movie clip in which to create the text field


depth

The depth in target on which to create the text field


x

The horizontal position of the text field


y

The vertical position of the text field


w

The width of the text field


h

The height of the text field

Example 16-5 shows the code for the TextFieldView class. Once again, pay special attention to the update( ) method, which receives log messages and handles the important task of displaying them on screen.

Example 16-5. The TextFieldView class
 import util.Observer; import util.Observable; import logger.Logger; import logger.LogMessage; /**  * An observer of the   Logger   class. This class displays  * messages sent to the log in an on-screen text field.  */ class logger.TextFieldView implements Observer {   // The log that this object is observing.   private var log:Logger;   // A reference to the text field.   private var out:TextField;   /**    * TextFieldView Constructor    */   public function TextFieldView (l:Logger,                                  target:MovieClip,                                   depth:Number,                                   x:Number,                                  y:Number,                                  w:Number,                                  h:Number) {     log = l;     makeTextField(target, depth, x, y, w, h);   }   /**    * Invoked when the log changes. For details, see the     *   Observer   interface in Example 16-2.    */   public function update (o:Observable, infoObj:Object):Void {     // Cast   infoObj   to a   LogMessage   instance for type checking.     var logMsg:LogMessage = LogMessage(infoObj);     // Display the log message in the log text field.     out.text += Logger.getLevelDesc(logMsg.getLevel( ))               + ": " + logMsg.getMessage( )  + "\n";     // Scroll to the bottom of the log text field.     out.scroll = out.maxscroll;   }   /**    * Creates a text field in the specified movie clip at    * the specified depth. Log messages are displayed in the text field.    */   public function makeTextField (target:MovieClip,                                depth:Number,                               x:Number,                               y:Number,                               w:Number,                               h:Number):Void {     // Create the text field.     target.createTextField("log_txt", depth, x, y, w, h);     // Store a reference to the text field.     out = target.log_txt;     // Put a border on the text field.     out.border = true;     // Make the text in the text field wrap.     out.wordWrap = true;   }   public function destroy ( ):Void {     log.removeObserver(this);     out.removeTextField( );   } } 

The completion of the TextFieldView class is a eureka! moment in which we can clearly see the fruits of our labor. With both the TextFieldView and OutputPanelView classes implemented, we now have two separate displays based on the same information source. When the Logger class receives a message, it doesn't have to worry about how the message is rendered. Instead, it merely broadcasts the message to its observers. The rendering and processing of the messages are handled by two completely separate observer classes. In our current example, we render the log in two ways, but once the general logging system is in place, it is trivial to add more log-rendering classes. For example, we could add a class that sends the log to a server-side database. Or we could add a class that archives the log locally and provides searching and arbitrary access to log messages. Each class neatly encompasses its own responsibilities. And the Logger class doesn't care whether there are three log-processor (observer) classes, a hundred such classes, or none.

Now let's put the final piece in the Observer puzzle, the Logger class.

16.2.4 The Logger Class

As the subject of our Observer implementation, the Logger class extends the Observable class, using it to handle the grunt work of managing observers and broadcasting state changes (in this case, log messages).

Here's the skeleton for the Logger class:

 class logger.Logger extends Observable { } 

To ensure that each application creates only one Logger instance, we use the Singleton design pattern, which we'll study in the next chapter. The following aspects of the Logger class are all part of the Singleton design pattern; we'll skip consideration of these items for now and return to them next chapter:

  • The Logger class stores an instance of itself in a static property called log

     private static var log:Logger = null; 

  • The Logger constructor function is private so that Logger instances cannot be created by outside code

  • Logger instances can be created only via Logger.getLog( )

     public static function getLog( ):Logger {   if (log == null) {     log = new Logger( );   }   return log;   } 

As we learned earlier, the Logger class maintains a log severity level that is used to filter log messages. The severity levels are stored in static properties, as follows:

 public static var FATAL:Number = 0; public static var ERROR:Number = 1; public static var WARN:Number  = 2; public static var INFO:Number  = 3; public static var DEBUG:Number = 4; 

Human-readable strings describing the severity levels are stored in the property levelDescriptions :

 public static var levelDescriptions = ["FATAL", "ERROR", "WARN", "INFO", "DEBUG"]; 

The current severity level is stored in an instance property, logLevel :

 private var logLevel:Number; 

To allow the log severity level to be set, the Logger class defines a setLevel( ) method:

 public function setLevel(lev:Number):Void {   lev = Math.floor(lev);   if (lev >= Logger.FATAL && lev <= Logger.DEBUG) {     logLevel = lev;     info("Log level set to: " + lev);     return;   }   warn("Invalid log level specified."); } 

To allow the log severity level to be retrieved, the Logger class defines a getLevel( ) method:

 public function getLevel( ):Number {   return logLevel; } 

A human-readable string representing a given log level can be retrieved via the class method getLevelDesc( ) :

 public static function getLevelDesc(level:Number):String {   return levelDescriptions[level]; } 

By default, the Logger constructor sets each Logger instance's severity level to 3 (INFO):

 private function Logger ( ) {   setLevel(Logger.INFO);    } 

And now the code we've been waiting for. To allow messages to be sent to the log, the Logger class provides five methods, corresponding to the five log levels: fatal( ) , error( ) , warn( ) , info( ) , and debug( ) . To send a message to the log, we first create a Logger instance:

 var log:Logger = Logger.getLog( ); 

Then we pass the message to be sent to the desired message-sending method. For example, the following code sends the message, "Something went wrong," with a severity of ERROR:

 log.error("Something went wrong"); 

The following code sends a message, "Application started!" with a severity of INFO:

 log.info("Application started!"); 

The most recent message sent to the log is stored in the instance property lastMsg :

 private var lastMsg:LogMessage; 

The methods fatal( ) , error( ) , warn( ) , info( ) , and debug( ) are all structured identically. Let's look at the code for the info( ) method to see how a log message is handled. Remember that the five message-sending methods are the state-change methods of the subject in our Observer pattern. As such, they follow a basic structure that all state-change methods in an Observer implementation follow. Here's the code for info( ) , with comments explaining each line:

 public function info(msg:String):Void {   // If the filter level is at least "INFO"...   if (logLevel >= Logger.INFO) {     // ...then  broadcast the message to observers.     // Using the supplied message string (   msg   ),      // create a   LogMessage   instance to send to observers.      // The   LogMessage   instance is the info object of this     // notification. Store the   LogMessage   instance in     //   lastMsg   for later retrieval.     lastMsg = new LogMessage(msg, Logger.INFO);     // Indicate that the subject has changed state.     setChanged( );     // Use   notifyObservers( )   to invoke   update( )   on all   Logger   // observers, passing the   LogMessage   instance as an argument.      // For the source code of   notifyObservers( )   , see Example 16-1.     notifyObservers(lastMsg);   } } 

The basic structure of the Logger.info( ) method is:

  • Perform state change (i.e., set lastMsg )

  • Create info object

  • Register state change in subject (via setChanged( ) )

  • Broadcast change notification (via notifyObservers( ) )

All state-change methods in Observable subclasses use the preceding structure.

Example 16-6 shows the code for Logger in its entirety.

Example 16-6. The Logger class
 import util.Observable; import logger.LogMessage; /**  * A general log class. Use   getLog( )   to create an app-wide instance.  * Send messages with   fatal( )   ,   error( )   ,   warn( )   ,   info( )   , and   debug( )   .  * Add views for the log with   addObserver( )   (views must implement   Observer   ).  *   * @version 1.0.0  */ class logger.Logger extends Observable {   // Static variable. A reference to the log instance (Singleton).   private static var log:Logger = null;   // The possible log levels for a message.   public static var FATAL:Number = 0;   public static var ERROR:Number = 1;   public static var WARN:Number  = 2;   public static var INFO:Number  = 3;   public static var DEBUG:Number = 4;   private var lastMsg:LogMessage;   // The human-readable descriptions of the preceding log levels.   public static var levelDescriptions = ["FATAL", "ERROR",                                           "WARN", "INFO", "DEBUG"];   // The zero-relative filter level for the log. Messages with a level    // above   logLevel   are not passed on to observers.   // Default is 3, "INFO" (only DEBUG messages are filtered out).   private var logLevel:Number;   /**    * Logger Constructor    */   private function Logger ( ) {     // Show "INFO" level messages by default.     setLevel(Logger.INFO);      }   /**    * Returns a reference to the log instance.    * If no log instance exists yet, creates one.    *    * @return   A Logger instance.    */   public static function getLog( ):Logger {     if (log == null) {       log = new Logger( );     }     return log;     }   /**    * Returns a human-readable string representing the specified log level.    */   public static function getLevelDesc(level:Number):String {     return levelDescriptions[level];   }   /**    * Sets the message filter level for the log.    *    * @param   lev   The level above which messages are filtered out.    */   public function setLevel(lev:Number):Void {     // Make sure the supplied level is an integer.     lev = Math.floor(lev);     // Set the log level if it's one of the acceptable levels.     if (lev >= Logger.FATAL && lev <= Logger.DEBUG) {       logLevel = lev;       info("Log level set to: " + lev);       return;     }     // If we get this far, the log level isn't valid.     warn("Invalid log level specified.");   }   /**    * Returns the message filter level for the log.    */   public function getLevel( ):Number {     return logLevel;   }   /**    * Returns the most recent message sent to the log.    */   public function getLastMsg( ):LogMessage {     return lastMsg;   }   /**    * Sends a message to the log, with severity "FATAL".    */   public function fatal(msg:String):Void {     // If the filter level is at least "FATAL", broadcast      // the message to observers.     if (logLevel >= Logger.FATAL) {       // Construct the log message object.       lastMsg = new LogMessage(msg, Logger.FATAL);       // Pass the message on to observers.       setChanged( );       notifyObservers(lastMsg);     }   }   /**    * Sends a message to the log, with severity "ERROR".    */   public function error(msg:String):Void {     // If the filter level is at least "ERROR", broadcast      // the message to observers.     if (logLevel >= Logger.ERROR) {       lastMsg = new LogMessage(msg, Logger.ERROR);       setChanged( );       notifyObservers(lastMsg);     }   }   /**    * Sends a message to the log, with severity "WARN".    */   public function warn(msg:String):Void {     // If the filter level is at least "WARN", broadcast     // the message to observers.     if (logLevel >= Logger.WARN) {       lastMsg = new LogMessage(msg, Logger.WARN);       setChanged( );       notifyObservers(lastMsg);     }   }      /**    * Sends a message to the log, with severity "INFO".    */   public function info(msg:String):Void {     // If the filter level is at least "INFO", broadcast     // the message to observers.     if (logLevel >= Logger.INFO) {       lastMsg = new LogMessage(msg, Logger.INFO);       setChanged( );       notifyObservers(lastMsg);     }   }   /**    * Sends a message to the log, with severity "DEBUG".    */   public function debug(msg:String):Void {     // If the filter level is at least "DEBUG", broadcast      // the message to observers.     if (logLevel >= Logger.DEBUG) {       lastMsg = new LogMessage(msg, Logger.DEBUG);       setChanged( );       notifyObservers(lastMsg);     }   } } 

Notice that the five message-sending methods in the Logger class ” fatal( ) , error( ) , warn( ) , info( ) , and debug( ) ” all contain nearly identical code. In this example, we'll accept that redundancy in order to repeatedly demonstrate the general structure of a state-change method. However, in a real-world version of the Logger class, we'd move the repeated code to a centralized method that checks the log level and notifies observers when appropriate. We'd call our centralized method handleMessage( ) and have each of the message-sending methods use it to validate and broadcast messages, as shown next:

 // The new   handleMessage( )   method. public function handleMessage(msg:String, msgSeverity:Number):Void {   if (logLevel >= msgSeverity) {     lastMsg = new LogMessage(msg, msgSeverity);     setChanged( );     notifyObservers(lastMsg);   } } // Here's a revised message-sending method,   debug( )   , // showing how   handleMessage( )   would be used. (Other  // message-sending methods are not shown.) public function debug(msg:String):Void {   handleMessage(msg, Logger.DEBUG); } 

16.2.5 Inheritance Misuse?

As we've just seen, in our implementation of the Logger class, we extend Observable . In our situation, the Logger class doesn't need to inherit from any other class, so it can extend Observable without issue. But what if the Logger class were already a subclass of some other class (say, MessageManager )? It wouldn't be able to inherit from the Observable class! Here, we encounter a classic misuse of inheritance ”extending a class simply to borrow its functionality. We first saw this kind of misuse in Chapter 6 and later showed how to avoid it in Chapter 8, under "Multiple Type Inheritance with Interfaces." Specifically, the Logger class doesn't have a legitimate "Is-A" relationship with the Observable class. That is, the Logger class is not a specialized kind of update broadcaster . Other classes won't use it in place of Observable for its additional broadcast features. On the contrary, the Logger class manages an application log and just happens to need the update broadcasting functionality found in Observable . In short, we've used inheritance to arrange a marriage of convenience between the Logger and Observable classes.

In a much more flexible implementation of the Observer pattern, we would define the Observable datatype as an interface, not as a concrete class. We would then create an implementation of that interface in a class named ObservableSubject . The ObservableSubject class would have all the features of the Observable class from Example 16-1. Our Logger class would not extend ObservableSubject . Instead, it would implement the Observable interface and make use of ObservableSubject via composition (exactly as the Rectangle class from Chapter 8 implemented Serializable and used the Serializer class via composition). The Logger class would then be able to inherit from a more natural superclass (again, perhaps a generic MessageManager class).

So, knowing that we're misusing inheritance in our Logger example, should we now alter our Observer implementation to allow subjects such as Logger to use a base class such as ObservableSubject via composition instead of inheritance? That depends on the situation. The composition approach is undeniably more flexible (adding justification to Sun's recommendation that every class that can be extended should also be usable via composition!). However, the composition approach is also more complex. In a simple application, that complexity can translate to unnecessary development time and source code that's harder to digest. Hence, a good rule to follow is one we've seen several times already: Never Add Functionality Early (for details on this and other Extreme Programming rules, see http://www.extremeprogramming.org/rules.html). So far our Logger class doesn't need to inherit from any other class, so we don't need to worry that we're "misusing" inheritance. We can wait until the program we're creating requires another subject that cannot inherit from Observable because it already inherits from another class. That's a good time to implement the composition version of Observer.

In this chapter, we'll stick with our inheritance-based Observer implementation. For comparison, however, Example 16-7 presents the composition-based version. Differences from the inheritance version are shown in bold. Refer to Chapter 8 for an explanation of the basic structure of composition. Note that the composition-based implementation of Observer uses, verbatim, the previous versions of the Observer interface and the LogMessage , OutputPanelView , and TextFieldView classes. The source code for those items is, hence, not repeated in Example 16-7. You can download the code shown in Example 16-7 from http:// moock .org/eas2/examples.

Example 16-7. Implementing the Observer pattern using composition
  // Code in   Observable.as   . This is the new   Observable   interface. import util.Observer; interface util.Observable {   public function addObserver(o:Observer):Boolean;   public function removeObserver(o:Observer):Boolean   public function notifyObservers(infoObj:Object):Void   public function clearObservers( ):Void;   public function hasChanged( ):Boolean;   public function setChanged( ):Void;   public function clearChanged( ):Void;   public function countObservers( ):Number; } // Code in   ObservableSubject.as   . The   ObservableSubject   class is nearly // identical to the previous   Observable   class, save for the commented // sections in bold. Note that   ObservableSubject   implements the new //   Observable   interface. Hence, the   Observable   interface must be imported.  import util.Observer;  import util.Observable; class util.ObservableSubject implements Observable {  private var changed:Boolean = false;   private var observers:Array;  // Constructor function, this time named   ObservableSubject   .   public function ObservableSubject ( ) {  observers = new Array( );   }   public function addObserver(o:Observer):Boolean {     if (o == null) {       return false;     }     for (var i:Number = 0; i < observers.length; i++) {       if (observers[i] == o) {         return false;       }     }     observers.push(o);     return true;   }   public function removeObserver(o:Observer):Boolean {     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;   }   public function notifyObservers(infoObj:Object):Void {     if (infoObj == undefined) {       infoObj = null;     }     if (!changed) {       return;     }     var observersSnapshot:Array = observers.slice(0);     clearChanged( );     for (var i:Number = observersSnapshot.length-1; i >= 0; i--) {       observersSnapshot[i].update(this, infoObj);     }   }   public function clearObservers( ):Void {     observers = new Array( );   }   public function setChanged( ):Void {     changed = true;   }   public function clearChanged( ):Void {     changed = false;   }   public function hasChanged( ):Boolean {     return changed;   }   public function countObservers( ):Number {     return observers.length;   } }  // Code in   Logger.as.   import util.Observable;          // Import the new   Observable   interface. import util.ObservableSubject;   // Import the new   ObservableSubject   class. import util.Observer; import logger.LogMessage; // Implement   Observable   , but don't extend   ObservableSubject   ! class logger.Logger implements Observable {   // An   ObservableSubject   instance, used to broadcast updates to observers.   private var subj:ObservableSubject;  private static var log:Logger = null;   public static var FATAL:Number = 0;   public static var ERROR:Number = 1;   public static var WARN:Number  = 2;   public static var INFO:Number  = 3;   public static var DEBUG:Number = 4;   private var lastMsg:LogMessage;   public static var levelDescriptions = ["FATAL", "ERROR",                                           "WARN", "INFO", "DEBUG"];   private var logLevel:Number;  // Create the   ObservableSubject   instance in the constructor.   private function Logger ( ) {     subj = new ObservableSubject( );  setLevel(Logger.INFO);     }   public static function getLog( ):Logger {     if (log == null) {       log = new Logger( );     }     return log;     }   public static function getLevelDesc(level:Number):String {     return levelDescriptions[level];   }   public function setLevel(lev:Number):Void {     // Make sure the supplied level is an integer.     lev = Math.floor(lev);     // Set the log level if it's one of the acceptable levels.     if (lev >= Logger.FATAL && lev <= Logger.DEBUG) {       logLevel = lev;       info("Log level set to: " + lev);       return;     }     // If we get this far, the log level isn't valid.     warn("Invalid log level specified.");   }   public function getLevel( ):Number {     return logLevel;   }   public function getLastMsg( ):LogMessage {     return lastMsg;   }   public function fatal(msg:String):Void {     if (logLevel >= Logger.FATAL) {       // Construct the log message object.       lastMsg = new LogMessage(msg, Logger.FATAL);       // Pass the message on to observers.       setChanged( );       notifyObservers(lastMsg);     }   }   public function error(msg:String):Void {     if (logLevel >= Logger.ERROR) {       lastMsg = new LogMessage(msg, Logger.ERROR);       setChanged( );       notifyObservers(lastMsg);     }   }   public function warn(msg:String):Void {     if (logLevel >= Logger.WARN) {       lastMsg = new LogMessage(msg, Logger.WARN);       setChanged( );       notifyObservers(lastMsg);     }   }      public function info(msg:String):Void {     if (logLevel >= Logger.INFO) {       lastMsg = new LogMessage(msg, Logger.INFO);       setChanged( );       notifyObservers(lastMsg);     }   }   public function debug(msg:String):Void {     if (logLevel >= Logger.DEBUG) {       lastMsg = new LogMessage(msg, Logger.DEBUG);       setChanged( );       notifyObservers(lastMsg);     }   }  // Wrapper methods for   ObservableSubject   methods follow. These methods   // subcontract their work out to the   ObservableSubject   instance,   subj   .   public function addObserver(o:Observer):Boolean {     return subj.addObserver(o);   }   public function removeObserver(o:Observer):Boolean {     return subj.removeObserver(o);   }   public function notifyObservers(infoObj:Object):Void {     subj.notifyObservers(infoObj);   }   public function clearObservers( ):Void {     subj.clearObservers( );   }   public function setChanged( ):Void {     subj.setChanged( );   }   public function clearChanged( ):Void {     subj.clearChanged( );   }   public function hasChanged( ):Boolean {     return subj.hasChanged( );   }   public function countObservers( ):Number {     return subj.countObservers( );   }  } 

16.2.6 Using the Logger Class

Now that we've seen how our subject ( Logger ) and its observers ( OutputPanelView and TextFieldView ) work individually, let's put them all together to form a functional logging system. The code in this section could go on a frame in the timeline of a .fla file or in a class (an .as file). Furthermore, the code works equally well with both the inheritance-based and composition-based implementations of the Observer pattern shown in this chapter.

First, we import the logger package (so we can refer to the Logger , LogMessage , OutputPanelView , and TextFieldView classes in that package directly):

 import logger.*; 

Then, we create a variable, log , to store our application's Logger instance:

 var log:Logger; 

Then we create the Logger instance:

 log = Logger.getLog( ); 

Next, we create two variables to store our Logger observers:

 var outputLogView:OutputPanelView; var textLogView:TextFieldView; 

Then we create our observer instances, passing each the Logger instance for this application:

 outputLogView = new OutputPanelView(log); textLogView = new TextFieldView(log, someMovieClip, 0, 50, 50, 300, 200); 

Finally, we use addObserver( ) to register our observers to receive updates from the Logger instance:

 log.addObserver(outputLogView); log.addObserver(textLogView); 

Our log's ready to go! Let's now try logging some messages:

 log.fatal("This is a non-recoverable problem."); log.error("This is a serious problem that may be recoverable."); log.warn("This is something to look into, but probably isn't serious."); log.info("This is a general bit of application information."); log.debug("This is a note that helps track down a bug."); 

If we executed the preceding code, the debug( ) message wouldn't appear because the Logger instance filters out debug( ) messages by default. To enable all messages for the log, we'd use:

 log.setLevel(Logger.DEBUG); 

If you want to see the log work on your own computer, you can download the example files from http://moock.org/eas2/examples.

 <  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