9.3 The Observer Pattern


The observer pattern is used when there are potentially many clients of a service or information flow from a single server. These clients may not even be known at design time. The pattern provides a simple way to dynamically wire clients with the server so that the clients can receive timely updates from the server.

9.3.1 Abstract

The observer pattern (sometimes called the publish-subscribe pattern) addresses the specific issue of how to notify a set of clients in a timely way that a value they care about has changed, especially when the notification process is to be repeated over a relatively long period of time. The basic solution offered by the observer pattern is to have the clients subscribe to the server to be notified about the value in question according to some policy. This policy can be "when the value changes," "at least every so often," "at most every so often," and so on. This minimizes computational effort for notification of clients, and across a communications bus minimizes the bus bandwidth required for notification of the appropriate clients.

9.3.2 Problem

The problem addressed by the observer pattern is how to notify some number of clients, in a timely fashion, of a data value according to some abstract policy, such as "when it changes," "every so often," "at most every so often," and "at least every so often." One approach is for every client to query the data value, but this can be computationally wasteful, especially when a client wants to be notified only when the data value changes. Another solution is for the server of this information to be designed to know its clients. However, we don't want to break the classic client-server model by giving the server knowledge about its clients. Doing so makes the addition of new clients a design change, which would be difficult to do dynamically at runtime.

9.3.3 Pattern Structure

Figure 9-1 shows the structure of the observer pattern. The structure expresses a very simple idea that the client should be dynamically coupled to the server with a subscription mechanism. Clients register or deregister for the data they want. When a client registers, it supplies the server with a way to send that information. Classically, this is a callback (method address), but it can also be an object ID or some other means to pass the value back to the client. When the policy indicates the data should be sent to the clients, the server looks up all the registered clients and sends them the data.

Figure 9-1. Observer Pattern

graphics/09fig01.gif

The observer pattern is deemed a mechanistic design pattern because the scope of the application of the pattern is collaboration-wide rather than system-wide. The observer pattern can be used as a basis for more elaborate patterns that employ its principle across distributed architectures as well [9].

9.3.4 Collaboration Roles

  • Abstract Client: The AbstractClient class associates with the AbstractSubject class so that it can invoke subscribe() and unsubscribe() as necessary. It contains an accept() operation called to accept the information required by the subscription, the address (as a notification handle instance) to be passed to the AbstractSubject instance to which it connects. There are many different ways to specify how to notify the client when new or updated information is available, but callbacks (pointers to an accept() operation of the client) are the most common.

  • AbstractSubject: The AbstractSubject class acts as a server of information desired by the AbstractClients. It accepts subscribe and unsubscribe requests from its clients. When the policy dictates that the AbstractSubject needs to notify its clients, it walks the client list to notify each. As noted in Section 9.3.6, a number of implementation means may be used to notify the subscribed clients.

  • NotificationHandle: The NotificationHandle class stores information for each client so that the Abstract Subject can notify the Abstract Client of the value stored in the Data class. The most common implementation strategy for object communication is to use a pointer (or a reference) to the AbstractClient::accept() operation. However, there are other means to implement NotificationHandles, such as local object identifiers, URLs, protocol-specific remote object IDs, network node port numbers, or even URLs.

  • ConcreteClient: The ConcreteClient is an application-specific subclass of the AbstractClient class. The pattern is applied by subclassing the AbstractClient and adding application-specific semantics into the new subclass.

  • ConcreteSubject: The ConcreteSubject is an application-specific subclass of the Abstract Subject class. The pattern is applied by subclassing the Abstract Subject and adding application-specific semantics to the subclass.

  • Data: The Data class contains the information that the Abstract Subject class knows and the AbstractClient class wants to know. The Data object containing the appropriate value may be shared with the clients either by reference (such as by passing a pointer to the single instance of the Data object) or by value (by copying the Data object for each of the subscribing clients). Such implementations are known as callbacks.

9.3.5 Consequences

The observer pattern simplifies the process of managing the sharing of values among a single server with possibly many clients. The simplification occurs in a number of ways. First, the observer pattern has runtime flexibility. It is easy at runtime to change the number of subscribers as well as the identity of the subscribers, because the Abstract Subject class does not need to have any information about its clients prior to their subscription. Further, all the information the AbstractSubject class needs can be provided by the clients during the subscription process. Second, a single policy regarding the timely or efficient updating of the clients can be centralized in the server and need not be replicated in the (potentially many) clients. This means the code that implements the notification policy needs to be running in only a single place (the server) rather than in many places (the individual clients).

9.3.6 Implementation Strategies

The primary points of variation in implementing the observer pattern are in the formulation of the NotificationHandle class, the implementation of the notification policy, and the type of data sharing.

The most common means for implementing object associations is a pointer (in C or C++) or a reference (in C++ or Java). A callback is a virtualized association and may use the same implementation. In this case, the one-to-many composition relation between the Abstract Subject and the NotificationHandle classes is implemented using an array or list of function pointers. When the server and the clients are not in the same address space, we use a proxy pattern.

The notification policy may be built into the AbstractSubject class and potentially overridden in the ConcreteSubject subclasses, or the strategy pattern may be employed. To use the strategy pattern, we reify the notification policy as a separate class that instructs the Abstract Subject when it is appropriate to notify the registered clients. The most common notification policies are

  • When the relevant data (data value) changes

  • Periodically

  • Both on change and periodically

The last primary implementation issue is how to pass the information around. The two primary approaches are by reference and by value. When data is passed by reference, the most common implementation is to pass a pointer or reference to the single Data object owned by the server. In this pattern it is important to ensure that the clients only read the information; the data should only be modified by the AbstractSubject instance. Further, if the data is to be shared among clients that may reside in different threads, care must be taken to protect the data from corruption due to mutual exclusion problems. The guarded call pattern can be used to ensure that the resource's integrity is maintained when data is shared across thread boundaries. When the data is shared by value, a copy of the Data object is made for each subscriber, which then has the explicit responsibility for destroying that object when it is no longer needed. This approach has the advantage that data protection issues go away but the disadvantage that more memory is needed and the issues around dynamic allocation, such as lack of timeliness predictability and memory fragmentation, must be dealt with.

9.3.7 Sample Model

Figure 9-2 shows a straightforward example of this simple pattern. Figure 9-2a shows the class structure of the model. Central to that structure is the WheelSensor, which acts as the «concrete subject» and the CruiseControl, Speedometer, and the AntispinController, which act as «concrete clients».

Figure 9-2a. Observer Pattern Example Structure

graphics/09fig02.gif

Figure 9-2b shows an example scenario of the execution of the structure shown in Figure 9-2a. In this scenario, each of the clients registers with the server by calling its subscribe operation. Later, when an evGetData event is sent to the server, the server walks the Callback List to find all the registered clients and sends them the data. It does this by calling the Callbacklist::getFirst and CallbackList::getNext() operations; these return a pointer to a client, which may then be dereferenced and the target object's accept() function called. When the CallbackList::getNext() operation returns NULL, the walk-through of the list is complete. At the end of the scenario, the Antispin Controller is sent an evDisable event and so it unsubscribes from the server.

Figure 9-2b. Observer Pattern Example Scenario

graphics/09fig02_01.gif



Real Time UML. Advances in The UML for Real-Time Systems
Real Time UML: Advances in the UML for Real-Time Systems (3rd Edition)
ISBN: 0321160762
EAN: 2147483647
Year: 2003
Pages: 127

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