Abstract. A common requirement of distributed systems is that they possess knowledge regarding the state of a remote object. Constantly checking that remote object for changes consumes client resources, server resources, and bandwidth. The observer pattern allows for client notification upon server changes.
Problem. The function of a client object is often to either represent some state present in a corresponding server object or to take an action on a server object change. Since the client object must have instant notification of changes to the server object’s state, thereare two possible solutions to the notification problem. The client object could check for changes every n units of time, or the server could notify the client only when a change
occurs. Having clients constantly check for server changes is a drain on resources (both client and server) and requires that much bandwidth be dedicated to this checking. The observer pattern discusses a logical manner by which clients can receive notification of server state change.
Context. This design pattern is applicable in any environment where client objects need constant knowledge of changes to some server-side value.
Forces. Server objects conforming to the observer pattern will spend time notifying clients of changes to server state. When implementing the server object, decisions need to be made about change synchronization and the manner in which resources are allocated to telling clients about changes. In some situations the server will want to notify the client before making the actual changes to itself. In other situations, the server object will want to immediately reflect the change internally and then send off client notification in a separate (possibly low-priority) thread. Additionally, since two-way communication between client and server is required, you must ensure that external security devices allow this. If a firewall protects the client from receiving method invocations, this pattern cannot be used.
Solution: The observer pattern functions in a manner quite similar to the Java JDK1.1 delegation event model. Under the observer pattern, clients expose a method (via an interface), which is then invoked to indicate a change in the server object. The client registers with the server as an interested listener. When changes occur in the server object, the server object sends information about the change to all clients. Figure 5.2 illustrates this process.
Note As with any distributed object environment, the concept of clients and servers is rather gray. Since the role of client or server may change during the application lifecycle, role should be thought of as a transient, not persistent, quality. Just because one piece of software is running on a Sun Sparc10 and the other is running on a 486 does not imply that the Sparc is the server and the 486 is the client. If the Sparc passes processing requests off to the 486, the 486 plays the role of the server.
Examples. The following examples demonstrate a simple application in which a client registers interest with a stock quote server. The client notifies the quote server of all interested ticker symbols, and the server notifies the client whenever one of those values changes. There are two classes and one interface contained in this application. The QuoteClientI interface (see Listing 5.4) identifies the method by which the client obtains notification of a change. The QuoteClient class (see Listing 5.5) implements the QuoteClientI interface and listens for changes to a few ticker symbols. The QuoteServer class (see Listing 5.6) tracks all listeners and sends notification whenever a registered symbol has a value change.
Figure 5.2: The observer pattern facilitates client notification of changes in server objects.
LISTING 5.4 THE QuoteClientI INTERFACE
/**
* Interface to be implemented by all object interested
* in receiving quote value changed events.
*/
public interface QuoteClientI {
public void quoteValueChanged(String sSymbol, double dNewValue);
}
LISTING 5.5 THE QuoteClient CLASS
/**
* The QuoteClient class registers interest with a server
* for tracking the values of different stocks. Whenever
* the server detects that a stock"s value has changed, it
* will notify the QuoteClient object by invoking the
* quoteValueChanged() method.
*
*/
import java.util.*;
public final class QuoteClient implements QuoteClientI { private Hashtable _hshPortfolio;
public QuoteClient() {
_hshPortfolio = new Hashtable();
regWithServer();
}
/**
* Registers with the server our interest in receiving * notification when certian stocks change value.
*/
private final void regWithServer() {
QuoteServer server = // bind to quote server server.regListener("INKT", this);
server.regListener("MOBI", this);
server.regListener("NGEN", this);
server.regListener("ERICY", this);
_hshPortfolio.put("INKT", new Double(0));
_hshPortfolio.put("MOBI", new Double(0));
_hshPortfolio.put("NGEN", new Double(0));
_hshPortfolio.put("ERICY", new Double(0));
} /**
* Invoked whenever the value associated with an interested * symbol changes.
*/
public void quoteValueChanged(String sSymbol, double dNewValue) {
// display the changes System.out.println("\n");
System.out.println(sSymbol+" changed value");
System.out.println("old value:
"+_hshPortfolio.get(sSymbol));
System.out.println("new value: "+dNewValue);
// store the new value
_hshPortfolio.put(sSymbol, new Double(dNewValue));
}
public static void main(String[] args)) { QuoteClient client = new QuoteClient();
} }
LISTING 5.6 THE QuoteServer CLASS
/**
* The QuoteServer class monitors stock feeds, and
* notfies interested parties when a change occurs
* to the value of a registered symbol.
*/
import java.util.*;
public final class QuoteServer {
// listeners are stored in a hashtable or vectors. the hashtable
// uses as a key the registered symbol, and as a value a Vector
// object containing all listners.
private Hashtable _hshListeners;
public QuoteServer() {
_hshListeners = new Hashtable();
} /**
* Send changed values to all listeners. Since the manner * in which the QuoteServer object monitors the stock * feeds is beyond the scope of this pattern, it is
* simply assumed that that method is invoked when needed.
*/
private void sendChangeForSymbol(String sSymbol, double dNewValue) {
// check if there are any listeners for this symbol Object o = _hshListeners.get(sSymbol);
if(o != null) {
Enumeration listeners = ((Vector)o).elements();
while(listeners.hasMoreElements()) {
((QuoteClientI)listeners.nextElement()).
ẻquoteValueChanged(sSymbol, dNewValue);
} } } /**
* Invoked by clients to register interest with the server for
* a specific symbol.
*/
public void regListener(String sSymbol, QuoteClientI client) {
// check if we already have a vector of listeners at this
ẻ location
Object o = _hshListeners.get(sSymbol);
if(o != null) {
((Vector)o).addElement(client);
}
else { // create the vector
Vector vecListeners = new Vector();
vecListeners.addElement(client);
hshListeners.put(sSymbol, vecListeners);
} } }
Resulting Context. Since the method defined by the client to indicate a server value change can be invoked at any time, clients must be developed with this in mind. Flow control cannot always be assumed, and any access to shared resources must be written in a thread-safe manner.
Rationale. The observer pattern allows for client synchronization with a server value in a manner that keeps resource use to a minimum. Since network traffic exists only when a value changes, no bandwidth is wasted. Additionally, their resources are used more efficiently because clients are not constantly pinging servers for change requests.
Known Uses. The observer pattern has obvious parallels in the world of push media. Push media involves pushing of content from some content source to a content listener. For example, instead of checking The New York Times Web site every day, push media delivers the information directly to your desktop whenever a change occurs.