Java Tip 29: How to decouple the Observer/Observable object model

Here's a tip, complete with code samples and illustrations, for optimizing the performance of updates to the Observer

Many applications have the need to update several objects or components with data from a common source. An example of this is a calculator program that, given some input parameters, displays data in several formats -- such as a pie chart, line graph, and tabular report. This type of application is commonly implemented using a Model View Controller (MVC) design pattern (see Gamma, Helm, Johnson, Vlissides,"Design Patterns," Addison-Wesley, 1994, ISBN 0-201-63361-2). Using MVC, a one-to-many relationship exists such that the data representing the current state of the model is communicated to N number of views. If the data changes, all of the views would be updated to reflect the new data.

In general, this one-to-many relationship of objects is defined by the Observer design pattern. Here, many Observers are related to a common object that provides the data of interest. Java provides the java.util.Observable class and the java.util.Observer interface for implementing the Observer/Observable model. The Observable is the object of interest that provides information to a set of Observers. For example, say there is an Observable class that receives messages from a TCP socket. Each message it receives is observed by three Observers: a DisplayObserver, DbObserver, and LogObserver (see the illustration below). The DisplayObserver is responsible for displaying the text of the message to a user screen. The dbObserver is responsible for extracting a SQL string out of the message, processing the SQL statement, and sending a results message back to the original sender. The LogObserver just logs each message that is received.

The following is some sample code for each of these Observers.

Note: The following sample code is not intended to be complete.

// sample Observable class that gets messages // from the network and sends the messages to // all observers public class MsgObservable extends Observable implements Runnable {

public MsgObservable(){} public void

run()

{

while( true )

{

msg = waitForMsg();

setChanged();

notifyObservers( msg );

}

} }

public class DisplayObserver implements Observer {

public DisplayObserver(){} public void

update( Observable obj, Object arg )

{

displayMsg( arg );

} }

public class DbObserver implements Observer {

public DbObserver() {} public void

update( Observable obj, Object arg )

{

dbCommand( arg );

} }

public class LogObserver implements Observer {

public LogObserver() {} public void

update( Observable obj, Object arg )

{

logTheMsg( arg );

} }

public class MyApplication { MyApplication()

{

// create a MsgObservable

// to get messages from the Network

MsgObservable msgObj = new MsgObservable(); // create the Observers

DisplayObserver display = new DisplayObserver();

DbObserver db = new DbObserver();

LogObserver log = new LogObserver(); // add the Observers to the Observable

msgObj.addObserver( display );

msgObj.addObserver( db );

msgObj.addObserver( log ); // start the MsgObservable

msgObj.start();

} }

In the sample code above, the MyApplication class contains the Observable msgObj, and Observers display db and log. The msgObj.addObserver() method is called to add each observer to the set of Observers known to the msgObj Observable. Note that addObserver() is a method in the Observable base class. The MsgObservable class is responsible for getting messages from the network and sending them to each Observer. This can be seen in the run() method, where it calls waitForMsg() and then sends the message to the set of Observers by calling setChanged() followed by notifyObservers( msg );. The call to notifyObservers( msg ) results in a call to each update() method of each Observer. Each observer can then process the message that it receives by its update() method.

Because the Observable calls the update() method of each Observer, the performance of the Observable could be tied to the performance of the update() methods in each Observer. Furthermore, if the update() method invokes other methods, each of those methods are also executing on the Observable's stack.

Tip: to minimize the performance impact on the Observable, decouple the Observers from the Observable using a threaded FIFO (first in, first out) queue. The threaded FIFO queue is inserted between the Observable and its Observers (see the illustration below). The threaded FIFO queue becomes the single Observer to the Observable, and the Observers become Observers of the threaded FIFO queue. The update() method in the threaded FIFO queue has one line of code: put( msg );.

Sample code of a threaded FIFO queue is shown below.

Note: This code is not intended to be complete.

public class ObserverQueue extends Observable implements Observer, Runnable {

ObserverQueue() {} public void

run()

{

while( true )

{

msg = get();

setChanged();

notifyObservers( msg );

}

} public void

update( Observable obj, Object arg )

{

put( arg );

} }

In the ObserverQueue sample code, note the class is both an Observable and an Observer. The actual methods to implement a FIFO queue have been omitted, but the basic functions are to place messages into the queue using the put() method, and getting messages from the queue using the get() method. The get() method would do a wait() until a message was available. The put and get methods are synchronized. The decoupling of the Observable occurs because the ObserverQueue is threaded. Its run() method is decoupled from the update() method. Therefore, the Observable can call the update() method and quickly place a message on the queue and return. The run() method then discovers a message is on the queue and sends that message to its set of Observers. When the run() method calls the update() methods of each Observer, the performance related to the update() method in each Observer does not impact the Observable. In essence, the Observers are decoupled from the Observable.

The application program would change as follows to insert an ObserverQueue between the Observers and the Observable:

public class MyApplication { MyApplication()

{

// create a MsgObservable

// to get messages from the Network

MsgObservable msgObj = new MsgObservable(); // create the Observers

DisplayObserver display = new DisplayObserver();

DbObserver db = new DbObserver();

LogObserver log = new LogObserver(); // create an ObserverQueue to decouple

// the Observers from the Observable

ObserverQueue observQueue = new ObserverQueue();

observerQueue.start(); // add the ObserverQueue as the single Observer

// to the MsgObservable

msg.addObserver( observQueue ); // add the Observers to the ObserverQueue

observQueue.addObserver( display );

observQueue.addObserver( db );

observQueue.addObserver( log ); // start the MsgObservable

msgObj.start();

} }

Using this technique, the application creates the MsgObservable object, which is responsible for getting messages from the network. It creates an ObserverQueue object, which is a threaded FIFO queue and functions as an Observer to the MsgObservable object and also functions as an Observable to the real Observers. When messages come in from the network to the MsgObservable object, it quickly updates the ObserverQueue, which places the message on its internal FIFO queue. The free running thread in the ObserverQueue object gets messages from the internal FIFO queue and updates each Observer with the message. This is done independently of the MsgObservable object. This decoupling technique should optimize the performance of the Observable by isolating or decoupling it from the dissemination of information to the Observers.

Albert Lopez was a member of the technical staff at Sun Microsystems from 1989 until 1995. He recently joined the Information Systems staff at the Chicago Board of Trade, where he is a lead member of the Java Development team developing the next-generation Electronic Trading System using Java. He is also a technical advisor for the CBOT Web site.
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more