Observer - Programming in the Large with Design Patterns (2012)

Programming in the Large with Design Patterns (2012)

Chapter 9. Observer

Introduction

Designing software as a loose confederation of cooperating objects often brings with it the need to maintain consistency between related objects (Gamma, et al. 1995). For example, consider an integrated development environment (IDE) with multiple views of the underlying source code. Most IDE’s offer an edit window along with other views such as an outline view and a type hierarchy.

Figure 64 IDE with multiple views of source code

A change to the underlying data through one of the views is immediately reflected in the other views that are visible. One way to implement this is to locate everything in one large tightly coupled class. This is a poor design choice though because it makes it impossible to reuse the views or the data structure independently. It also complicates maintenance. Adding a new view would require modifying the existing class.

A better design is one that organizes the views and underlying data structure into separate loosely coupled cooperating objects. This facilitates reuse and extension but also creates the problem of how to keep the views in sync with the underlying data. One option is to have view objects repeatedly poll the underlying data structure for changes. This would be a waste of resources though because in most cases there would be no changes. A more efficient solution is to use the Observer design pattern.

With the Observer design pattern a subject object (the underlying data structure in this example) keeps a list of observers (views in this example). When the state of the subject changes, observers are notified and given a chance to synchronize with the subject.

Intent

The Observer design pattern defines a one-to-many relationship between a subject object and any number of observer objects such that when the subject object changes, observer objects are notified and given a chance to react to changes in the subject.

Solution

The three main components of the Observer design pattern are: Subject, Observer and ConcreteObserver.

Figure 65 Static structure for Observer design pattern

The Subject is responsible for:

o Attaching and detaching observers

o Notifying attached observers when the state of the subject changes

o Application-specific state and logic

Observer defines an abstract interface with the callback operation subject objects use to notify observers of a change. A subject is loosely coupled to its observers because the subject only knows about its observers through the abstract interface Observer.

ConcreteObserver implements the abstract interface Observer. ConcreteObserver may keep a reference to the Subject it is observing. (The other option is to pass a reference to the subject on the update() method.) The reference to the subject is used to query the subject for more details when notified of a change in the state of the subject.

The pseudo code in the UML static diagram above gives some indication of the dynamic interaction between components. The following sequence diagram clarifies typical interaction by showing the order of operations for one scenario of use.

Figure 66 Dynamic interactions in Observer design pattern

(1) During the setup phase observers are registered with the subject. Observers may attach themselves or be added to the subject via a third party.

(2) A change in the state of the subject may be initiated from a third party or one of the views.

(3) After a change in the state of the subject, the subject will notify registered observers. The sequence diagram above shows notifyObservers() being called from within the subject at the end of a state-modifying operation. In some cases, though, it may be more efficient to let clients decide when notifyObservers() is called. For example, if a client plans to make several changes to a data structure representing an on screen object, it might be more efficient to have clients call notifyObservers() after calling a series of state-changing operations on the subject data structure. Having the subject call notifyObservers() after every state-changing operation simplifies the design from the client’s perspective but also may result in unnecessary intermediate updates.

(4) When notified of a change in the subject, observers may query the subject for the details of what has changed (the pull method). Another option is for subjects to forward information about the change to observers through the update method (the push method). The push method is probably more efficient but it increases the coupling from subject to observers making it harder to reuse observers in other contexts.

Logic in the subject for attaching, detaching and notifying observers is the same for all subjects. One way to avoid repeating this logic in different subjects is to encapsulate the logic in a separate reusable class Subject that is inherited by all concrete subjects.

Figure 67 Alternate organization for Observer design pattern

Sample Code

The Java program in this section has one subject with two observers. The subject is Stock, a class that encapsulates the name and price of a stock. One of the observers is StockView, a class that prints the current symbol and price of the stock it is observing. The other observer is StockTrader, a class that watches the price of a stock and sells when the stock reaches a certain price.

Figure 68 Class diagram for sample code

The following code creates an instance of a stock with two observers and then changes the price of the stock three times.

import java.util.ArrayList;

public class ObserverRunner {

public static void main(String[] args) {

Stock stock = new Stock("IBM",250);

StockView sv = new StockView(stock);

StockTrader st = new StockTrader(stock,253);

stock.setPrice(251);

stock.setPrice(252);

stock.setPrice(253);

}

}

interface Observer {

void update();

}

class Subject {

private ArrayList observers = new ArrayList();

public void attach(Observer o) {

observers.add(o);

}

public void detach(Observer o) {

observers.remove(o);

}

public void notifyObservers() {

for (Object o : observers) {

Observer observer = (Observer)o;

observer.update();

}

}

}

class Stock extends Subject {

private String tickerSymbol;

private int price;

public Stock(String tickerSymbol, int price) {

this.tickerSymbol = tickerSymbol;

this.price = price;

}

public String getSymbol() {

return tickerSymbol;

}

public int getPrice() {

return price;

}

public void setPrice(int price) {

this.price = price;

notifyObservers();

}

}

class StockView implements Observer {

private Stock stock;

public StockView(Stock stock) {

this.stock = stock;

stock.attach(this);

}

public void update() {

System.out.println(stock.getSymbol() +

" is selling for " + stock.getPrice());

}

}

class StockTrader implements Observer {

private Stock stock;

private int sellPrice;

public StockTrader(Stock stock, int sellPrice) {

this.stock = stock;

this.sellPrice = sellPrice;

stock.attach(this);

}

public void update() {

if (stock.getPrice() >= sellPrice)

System.out.println("Sell " + stock.getSymbol() + "!");

}

}

The output of the program above is:

IBM is selling for 251

IBM is selling for 252

IBM is selling for 253

Sell IBM!

Discussion

The example in the previous section used the Observer design pattern without any help from the Java Class Library. Java, like many other languages, provides a framework in its standard library for implementing the Observer design pattern. It defines a class Observable and an interfaceObserver.

Figure 69 Java’s support for the Observer design pattern

Subject and Observer in the previous example can be replaced by Observerable and Observer from the Java Class Library, saving more than 20 lines of code.

Java’s framework for implementing the Observer design pattern also shows how the pattern can be extended to support both the push and pull methods of update discussed earlier. The class Observable has two methods for notifying observers:

notifyObservers()

notifyObservers(Object arg)

The first method notifies observers without any extra information about the change in the state of the subject. The second method is used to push state changes directly to observers. The value passed arrives at observers as the second parameter on the callback method update(Observerable obs, Object arg). When the first method is used to notify observers the second parameter on the callback method is null.

The first parameter on the callback method update() points out another variation on the Observer design pattern. Observers observing multiple subjects need some why of identifying the source of a notification. The first parameter of the update method is a reference to the Observable object calling the update method. Here is an example of how observers might use this parameter to distinguish between updates coming from multiple subjects.

class ConcreteObserver implements Observer {

Observable subject1;

Observable subject2;

. . .

public void update(Observerable obs, Object arg) {

if (obs == subject1) {

. . .

} else if (obs == subject2) {

. . .

}

}

}

Related Patterns

The Observer design pattern is used in the Model-View-Controller architecture. The data model is the subject and views are observers.