Observer pattern Back | TOC

Where do I need a pattern?

Suppose you had a list box and a table view that contained the same data. Whenever the data changes, you want those changes to reflect immediately in the list box and the table view without your intervention.

This is a common situation when you have multiple views of the same data. This is a recurring problem in GUI-based programming, so you want a design solution that can be re-used whenever the context arises.

You identify a pattern here. You see a recurring problem. Though you may devise a mechanism to solve it, you are better off with a proven solution.

The Observer design pattern addresses this problem and provides a proven solution that is re-usable in similar contexts. The solution runs like so:

1. Encapsulate data in an object. Call it the Subject.
2. The views are each encapsulated in objects called the Observers.
3. The observers register their interest with the Subject for changes in the data.
4. The Subject notifies the observers whenever the data changes.
5. The observers then update their views.

In a language such as Java, we use the interface mechanism to implement this pattern.

/* 
* interface Subject that is implemented by the class that encapsulates data 
* allows observers to register themselves
*/
public interface Subject {
// provide a method for the observers to register their interest
public void register(Observer obs);
}

/* 
* interface Observer that is implemented by the classes that encapsulate different views 
* allows observers to be notified by the Subject
*/
public interface Observer {
// method which the Subject calls to notify registered observers of data changes
public void notify(String msg);
}

The Observer pattern and Java

The Observer pattern, as you can see, separates data from the view that displays it. This kind of loose coupling not only allows data to be modeled independently of the display objects, but also keeps the Subject ignorant of the specifics of implementation views. The Observer design pattern falls into the Behavioral Patterns category. The other categories of design patterns are: Creational and Structural. (I intend to write a piece on patterns listed in these categories, so keep looking!) 

Separation of data model from the views is a recurring theme in object-oriented programming. It is the idea behind the model-view-controller (MVC) architecture. The observer pattern is a special case of the MVC pattern, where the controller is attached either to the data model or to the views.

In Java the observer pattern is used extensively in the Java Foundation Classes (JFC). The JList, JTree and the JTable components all manipulate data through their respective data models. The components act as observers of their data models.

In the java code snippet shown above, notice how the interface mechanism of Java maps nicely to the data and view objects. Subject represents data, no matter which object implements it. Similarly, the view objects implement the Observer interface, irrespective of the display mode. 

You may be wondering why I added a 'String msg' parameter to the notify method in the Observer interface. This is useful to notify the type of change in the data, so that the observer may choose to ignore. This is necessary when you only make small incremental changes that a particular view may not be interested in.

Instead of a simple string, a more complex object that encapsulates all possible types of changes to the data may be passed. The observers can query the object for the changes they are interested in, and act accordingly. This is the approach taken by Java in providing a re-usable observer pattern. In the java.util package, we have the Observer interface and the Observable class.

The Observer interface has just one method:
public void update(Observable o, Object arg)

Whenever changes in the Observer occur, this method is called automatically. The observable is a concrete class, so the class deriving from it must be determined upfront, as Java allows only single inheritance. The Observable class models the Subject discussed above.

Example

Let us see an example. To keep things simple, we will avoid the graphics components and output to the console. The data model extends the Observable class, and has the driver code in the main method. The data is updated as a new color is added to the instance of this class.

Two observers observe the data model class. They register their interest via the addObserver method in the Observable class and get notified whenever a new color is added to the data model. 

The notifyObservers method in the Observable class calls update method in the Observers when a new color is added to it.

Here are the steps and the data used to drive the program.
1. Add a color to the data model.
2. Update values in RGB and Hex

color - RGB - Hex
-------------------------------------
white - 255,255,255 - #FFFFFF 
red - 255,0,0 - #FF0000
green - 0,255,0 - #00FF00
blue - 0,0,255 - #0000FF
black - 0,0,0 - #000000
--------------------------------------

Code listings follow.

Listing 1: The data model

import java.util.Observable;
import java.awt.Color;

public class ColorDataModel extends Observable {

String rgb = "";
String hex = "";

public ColorDataModel() {
// register observers
addObserver(new ColorViewRGB());
addObserver(new ColorViewHex());
}

public void addColor(Color c) {

int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();

rgb = r+","+g+","+b;
String rh = Integer.toHexString(r);
if(rh.equals("0")) {
rh = "00";
}
String gh = Integer.toHexString(g);
if(gh.equals("0")) {
gh = "00";
}
String bh = Integer.toHexString(b);
if(bh.equals("0")) {
bh = "00";
}
hex = "#"+rh+gh+bh;

// notify changes to observers
setChanged();
notifyObservers(rgb+"|"+hex);
clearChanged();
}

// the driver code
public static void main(String[] args) {

ColorDataModel model = new ColorDataModel();
System.out.println("--------------------------");
System.out.println("Color added is white");
model.addColor(Color.white);

System.out.println("--------------------------");
System.out.println("Color added is red");
model.addColor(Color.red);

System.out.println("--------------------------");
System.out.println("Color added is green");
model.addColor(Color.green);

System.out.println("--------------------------");
System.out.println("Color added is blue");
model.addColor(Color.blue);

System.out.println("--------------------------");
System.out.println("Color added is black");
model.addColor(Color.black);

}

}

Listing 2: Observer 1

import java.util.Observer;
import java.util.Observable;

public class ColorViewRGB implements Observer {

public ColorViewRGB() {

}

// update is called when Subject (Observable) calls notifyObservers method. 
public void update(Observable o, Object arg) {
String s = (String)arg;
int delim = s.indexOf("|");
System.out.println("From Observer ColorViewRGB");
System.out.println(s.substring(0, delim));

}
}

Listing 3: Observer 2

import java.util.Observer;
import java.util.Observable;

public class ColorViewHex implements Observer {

public ColorViewHex() {

}

// update is called when Subject (Observable) calls notifyObservers method. 
public void update(Observable o, Object arg) {
String s = (String)arg;
int delim = s.indexOf("|");
System.out.println("From Observer ColorViewHex");
System.out.println(s.substring(delim+1));
}
}

When you compile and run the code listed above, you will see the output as under:
--------------------------
Color added is white
From Observer ColorViewHex
#ffffff
From Observer ColorViewRGB
255,255,255
--------------------------
Color added is red
From Observer ColorViewHex
#ff0000
From Observer ColorViewRGB
255,0,0
--------------------------
Color added is green
From Observer ColorViewHex
#00ff00
From Observer ColorViewRGB
0,255,0
--------------------------
Color added is blue
From Observer ColorViewHex
#0000ff
From Observer ColorViewRGB
0,0,255
--------------------------
Color added is black
From Observer ColorViewHex
#000000
From Observer ColorViewRGB
0,0,0