An inside view of Observer

The Observer pattern facilitates communication between decoupled objects

Not long ago my clutch gave out, so I had my Jeep towed to a local dealership. I didn't know anybody at the dealership, and none of them knew me, so I gave them my telephone number so they could notify me with an estimate. That arrangement worked so well we did the same thing when the work was finished. Because this all turned out perfectly for me, I suspect the service department at the dealership employs the same pattern with most of its customers.

This publish-subscribe pattern, where an observer registers with a subject and subsequently receives notifications, is quite common, both in everyday life and in the virtual world of software development. In fact, the Observer pattern, as it is known, is one of the linchpins of object-oriented software development because it lets dissimilar objects communicate. That ability lets you plug objects into a framework at runtime, which allows for highly flexible, extensible, and reusable software.

Note: You can download this article's source code from Resources.

The Observer pattern

In Design Patterns, the authors describe the Observer pattern like this:

Define a one to many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

The Observer pattern has one subject and potentially many observers. Observers register with the subject, which notifies the observers when events occur. The prototypical Observer example is a graphical user interface (GUI) that simultaneously displays two views of a single model; the views register with the model, and when the model changes, it notifies the views, which update accordingly. Let's see how it works.

Observers in action

The application shown in Figure 1 contains one model and two views. The model's value, which represents image magnification, is manipulated by moving the slider knob. The views, known as components in Swing, are a label that shows the model's value and a scroll pane that scales an image in accordance with the model's value.

Figure 1. Observers in action. Click on thumbnail to view full-size image.

The model in the application is an instance of DefaultBoundedRangeModel(), which tracks a bounded integer value—in this case from 0 to 100—with these methods:

  • int getMaximum()
  • int getMinimum()
  • int getValue()
  • boolean getValueIsAdjusting()
  • int getExtent()
  • void setMaximum(int)
  • void setMinimum(int)
  • void setValue(int)
  • void setValueIsAdjusting(boolean)
  • void setExtent(int)
  • void setRangeProperties(int value, int extent, int min, int max, boolean adjusting)
  • void addChangeListener(ChangeListener)
  • void removeChangeListener(ChangeListener)

As the last two methods listed above indicate, instances of DefaultBoundedRangeModel() support change listeners. Example 1 shows how the application takes advantage of that feature:

Example 1. Two observers react to model changes

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Test extends JFrame {
      private DefaultBoundedRangeModel model =
                  new DefaultBoundedRangeModel(100,0,0,100);
      private JSlider slider = new JSlider(model);
      private JLabel readOut = new JLabel("100%");
      private ImageIcon image = new ImageIcon("shortcake.jpg");
      private ImageView imageView = new ImageView(image, model);
      public Test() {
            super("The Observer Design Pattern");
            Container contentPane = getContentPane();
            JPanel panel = new JPanel();
            panel.add(new JLabel("Set Image Size:"));
            panel.add(slider);
            panel.add(readOut);
            contentPane.add(panel, BorderLayout.NORTH);
            contentPane.add(imageView, BorderLayout.CENTER);
            model.addChangeListener(new ReadOutSynchronizer());
      }
      public static void main(String args[]) {
            Test test = new Test();
            test.setBounds(100,100,400,350);
            test.show();
      }
      class ReadOutSynchronizer implements ChangeListener {
            public void stateChanged(ChangeEvent e) {
                  String s = Integer.toString(model.getValue());
                  readOut.setText(s + "%");
                  readOut.revalidate();
            }
      }
}
class ImageView extends JScrollPane {
      private JPanel panel = new JPanel();
      private Dimension originalSize = new Dimension();
      private Image originalImage;
      private ImageIcon icon;
      public ImageView(ImageIcon icon, BoundedRangeModel model) {
            panel.setLayout(new BorderLayout());
            panel.add(new JLabel(icon));
            this.icon = icon;
            this.originalImage = icon.getImage();
            setViewportView(panel);
            model.addChangeListener(new ModelListener());
            originalSize.width = icon.getIconWidth();
            originalSize.height = icon.getIconHeight();
      }
      class ModelListener implements ChangeListener {
            public void stateChanged(ChangeEvent e) {
              BoundedRangeModel model =
               (BoundedRangeModel)e.getSource();
                  if(model.getValueIsAdjusting()) {
                        int min = model.getMinimum(), 
                              max = model.getMaximum(), 
                              span = max - min,
                              value = model.getValue();
                        double multiplier = (double)value / (double)span;
                        multiplier = multiplier == 0.0 ? 
                                           0.01 : multiplier;
                        
                        Image scaled = originalImage.getScaledInstance(
                              (int)(originalSize.width * multiplier),
                              (int)(originalSize.height * multiplier),
                              Image.SCALE_FAST);
                        icon.setImage(scaled);
                        panel.revalidate();
                        panel.repaint();
                  }
            }
      }
}

When you move the slider knob, the slider changes its model's value. That change triggers event notifications to the two change listeners registered with the model, which adjust the readout and scale the image. Both listeners use the change event passed to

stateChanged()

to determine the model's new value.

Swing is a heavy user of the Observer pattern—it implements more than 50 event listeners for implementing application-specific behavior, from reacting to a pressed button to vetoing a window close event for an internal frame. But Swing is not the only framework that puts the Observer pattern to good use—it's widely used in the Java 2 SDK; for example: the Abstract Window Toolkit, the JavaBeans framework, the javax.naming package, and input/output handlers.

Example 1 specifically shows use of the Observer pattern with Swing. Before we discuss more Observer pattern details, let's look at how the pattern is generally implemented.

How the Observer pattern works

Figure 2 shows how objects in the Observer pattern are related.

Figure 2. Observer pattern class diagram

The subject, which is an event source, maintains a collection of observers and provides methods to add and remove observers from that collection. The subject also implements a notify() method that notifies each registered observer about events that interest the observer. Subjects notify observers by invoking the observer's update() method.

Figure 3 shows a sequence diagram for the Observer pattern.

Figure 3. Observer pattern interaction. Click on thumbnail to view full-size image.

Typically, some unrelated object will invoke a subject's method that modifies the subject's state. When that happens, the subject invokes its own notify() method, which iterates over the collection of observers, calling each observer's update() method.

The Observer pattern is one of the most fundamental design patterns because it allows highly decoupled objects to communicate. In Example 1, the only thing the bounded range model knows about its listeners is that they implement a stateChanged() method. The listeners are only interested in the model's value, not how the model is implemented. The model and its listeners know very little about one another, but thanks to the Observer pattern, they can communicate. That high degree of decoupling between models and listeners lets you build software composed of pluggable objects, making your code highly flexible and reusable.

The Java 2 SDK and the Observer pattern

The Java 2 SDK provides a classic implementation of the Observer pattern with the Observer interface and the Observable class from the java.util directory. The Observable class represents the subject; observers implement the Observer interface. Interestingly, this classic Observer pattern implementation is rarely used in practice because it requires subjects to extend the Observable class. Requiring inheritance in this case is a poor design because potentially any type of object is a subject candidate, and because Java doesn't support multiple inheritance; often, those subject candidates already have a superclass.

The event-based implementation of the Observer pattern, which was used in the preceding example, is the overwhelming choice for Observer pattern implementation because it doesn't require subjects to extend a particular class. Instead, subjects follow a convention that requires the following public listener registration methods:

  • void addXXXListener(XXXListener)
  • void removeXXXListener(XXXListener)

Whenever a subject's bound property (a property that's been observed by listeners) changes, the subject iterates over its listeners and invokes the method defined by the XXXListener interface.

By now you should have a good grasp of the Observer pattern. The rest of this article focuses on some of the Observer pattern's finer points.

Anonymous inner classes

In Example 1, I used inner classes to implement the application's listeners, because the listener classes were tightly coupled to their enclosing class; however, you can implement listeners any way you desire. One of the most popular choices for handling user interface events is the anonymous inner class, which is a class with no name that's created in-line, as demonstrated in Example 2:

Example 2. Implement observers with anonymous inner classes

...
public class Test extends JFrame {
   ...
      public Test() {
         ...
            model.addChangeListener(new ChangeListener() {
                  public void stateChanged(ChangeEvent e) {
                        String s = Integer.toString(model.getValue());
                        readOut.setText(s + "%");
                        readOut.revalidate();
                  }
            });
      }
      ...
}
class ImageView extends JScrollPane {
      ...
      public ImageView(final ImageIcon icon, BoundedRangeModel model) {
      ...
            model.addChangeListener(new ChangeListener() {
                  public void stateChanged(ChangeEvent e) {
                    BoundedRangeModel model =
                     (BoundedRangeModel)e.getSource();
                        if(model.getValueIsAdjusting()) {
                              int min = model.getMinimum(), 
                                    max = model.getMaximum(), 
                                    span = max - min,
                                    value = model.getValue();
                              double multiplier = (double)value / (double)span;
                              multiplier = multiplier == 0.0 ? 
                                                             0.01 : multiplier;
                        
                              Image scaled = originalImage.getScaledInstance(
                                    (int)(originalSize.width * multiplier),
                                    (int)(originalSize.height * multiplier),
                                    Image.SCALE_FAST);
                              icon.setImage(scaled);
                              panel.revalidate();
                        }
                  }
            });
      }
}

Example 2's code is functionally equivalent to Example 1's code; however, the code above uses anonymous inner classes to define the class and create an instance in one fell swoop.

JavaBeans event handler

Using anonymous inner classes as shown in the previous example was very popular with developers, so starting with Java 2 Platform, Standard Edition (J2SE) 1.4, the JavaBeans specification has taken the responsibility for implementing and instantiating those inner classes for you with the EventHandler class, as shown in Example 3:

Example 3. Using java.beans.EventHandler

import java.beans.EventHandler;
...
public class Test extends JFrame {
   ...
      public Test() {
      ...
            model.addChangeListener(EventHandler.create(
                                    ChangeListener.class, this,
                                   "updateReadout"));
      }
   ...
      public void updateReadout() {
            String s = Integer.toString(model.getValue());
            readOut.setText(s + "%");
            readOut.revalidate();
      }
}
...

Using reflection, the static EventHandler.create() method creates a listener that implements the specified interface by invoking the specified method (in this case, updateReadout()) on the specified target (this). You lose some type safety with this approach, but perhaps contrary to your intuition, the EventHandler.create() can be faster than handcoding listeners because the EventHandler class can cache listeners.

Before we discuss implementing the Observer pattern in your code, let's examine a variation of the Observer pattern where observers can veto proposed events.

Observers that can veto

Most of the time, listeners (a.k.a. observers) react to events that have already occurred. But sometimes, listeners need veto power to prevent an upcoming event from occurring. For example, consider the common scenario depicted in Figures 4a and 4b: a listener listens for window close events (Figure 4a) and displays a dialog box that lets the user save changes (Figure 4b). If the user clicks the Cancel button, the listener will veto the window close event.

Figure 4a. A listener listens for window close events
Figure 4b. A dialog box lets users save changes

The application shown in Figure 4 is listed in Example 4:

Example 4. Vetoable observers

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import java.util.*;
public class Test extends JFrame {
   private JDesktopPane desktopPane = new JDesktopPane();
   public Test() {
      Container contentPane = getContentPane();
      contentPane.add(desktopPane, BorderLayout.CENTER);
      desktopPane.setLayout(new FlowLayout());
      JInternalFrame jif = new JInternalFrame(
            "A Window with a Vetoable Change Listener", // Title
            false,  // Resizable
            true);  // Closable
      jif.setPreferredSize(new Dimension(400, 350));
      jif.show();
      jif.addVetoableChangeListener(new CloseListener());
      desktopPane.add(jif);
   }
   public static void main(String args[]) {
      GJApp.launch(new Test(), 
               "Vetoing Internal Frame Closing",
               300,300,550,400);
   }
}
class CloseListener implements VetoableChangeListener { 
   private Test applet;
   public void vetoableChange(PropertyChangeEvent e) 
                           throws PropertyVetoException {
      String name = e.getPropertyName();
      if(name.equals(JInternalFrame.IS_CLOSED_PROPERTY)) {
         Component internalFrame = (Component)e.getSource();
         Boolean oldValue = (Boolean)e.getOldValue(),
                   newValue = (Boolean)e.getNewValue();
         if(oldValue == Boolean.FALSE && 
            newValue == Boolean.TRUE) {
            int answer = JOptionPane.showConfirmDialog(
                  internalFrame,      // ParentComponent
                  "Save Changes?",    // Message
                  "Unsaved Changes",    // Title
                  JOptionPane.YES_NO_CANCEL_OPTION); 
            if(answer == JOptionPane.CANCEL_OPTION) {
               throw new PropertyVetoException("close cancelled", e);
            }
         }
      }
   }
}
class GJApp extends WindowAdapter {
   // Application class not listed here for the sake of brevity.
}

The difference between regular change listeners and vetoable change listeners is that the latter can throw a PropertyVetoException(), as illustrated in Example 4. Throwing the exception rolls back the change, in this case, the window close operation, so the window stays open.

Now that you know how to use the Observer pattern's Swing and JavaBeans implementations, let's see how you can implement the Observer pattern in your own code.

Implement the Observer pattern

This section extends an example from a previous Java Design Patterns column, "Take Control with the Proxy Design Pattern." In that example, I implemented a proxy for Swing image icons that creates images on demand. In this section, I extend that example by implementing the Observer pattern: when the proxy finishes loading its image, it fires a property change event, and a listener displays a dialog box.

The application, shown in Figures 5a, 5b, and 5c, contains a regular Swing image icon on the left and an image icon proxy on the right. The icon loads its image when it's created, so it appears when the application starts. The proxy, on the other hand, draws a rectangle around its perimeter and displays the message Loading image... until the image loads. Subsequently, when the image loads, the proxy paints the image and fires a property change event. An application-specific listener reacts to the property change event by displaying a dialog box, as shown in Figure 5c.

Figure 5a. Proxy begins loading image
Figure 5b. Proxy finishes loading image and fires a property change event
Figure 5c. Listener reacts to the property change and displays a dialog box

The application, shown in Figures 5a, 5b, and 5c, is listed in Example 5:

Example 5. Observe a proxy object

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.util.ArrayList;
// This class tests a virtual proxy, which is a proxy that
// delays loading an expensive resource (an icon) until that
// resource is needed.
public class VirtualProxyTest extends JFrame {
   private static String IMAGE_NAME = "mandrill.jpg";
   private static int IMAGE_WIDTH = 256, IMAGE_HEIGHT = 256,
                          SPACING = 5,        FRAME_X = 150,
                          FRAME_Y = 200, FRAME_WIDTH = 530, 
                          FRAME_HEIGHT = 286;
   private Icon imageIcon = null, imageIconProxy = null;
   static public void main(String args[]) {
      VirtualProxyTest app = new VirtualProxyTest();
      app.show();
   }
   public VirtualProxyTest() {
      super("Virtual Proxy Test");
      // Create an image icon and an image icon proxy.
      imageIcon = new ImageIcon(IMAGE_NAME);
      imageIconProxy = new ImageIconProxy(IMAGE_NAME,
                                          IMAGE_WIDTH, 
                                          IMAGE_HEIGHT);
            ((ImageIconProxy)imageIconProxy).addPropertyChangeListener(
                  new PropertyChangeListener() {
                  public void propertyChange(PropertyChangeEvent e) {
      JOptionPane.showMessageDialog(VirtualProxyTest.this,"Image Loaded");
                  }
            });
      // Set the bounds of the frame and the frame's default.
      // Close operation.
      setBounds(FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }
   public void paint(Graphics g) {
      super.paint(g);
      Insets insets = getInsets();
      imageIcon.paintIcon(this, g, insets.left, insets.top);
      imageIconProxy.paintIcon(this, g, 
               insets.left + IMAGE_WIDTH + SPACING, // width
               insets.top); // height
   }
}
// ImageIconProxy is a proxy (or surrogate) for an icon.
// The proxy delays loading the image until the first time the
// image is drawn. While the icon is loading its image, the
// proxy draws a border and the message "Loading image...".
//
// This version of the ImageIconProxy class makes the real icon a bound
// property: when the property changes, ImageIconProxy fires a
// PropertyChangeEvent to all interested property change listeners.
class ImageIconProxy implements javax.swing.Icon {
      private ArrayList propertyChangeListeners = new ArrayList();
   private Icon realIcon = null;
   private boolean isIconCreated = false;
   private String  imageName;
   private int     width, height;
   public ImageIconProxy(String imageName, int width, int height){
      this.imageName = imageName;
      this.width = width;
      this.height = height;
   }
   public int getIconHeight() {
      return realIcon == null ? height : realIcon.getIconHeight(); 
   }
   public int getIconWidth() {
      return realIcon == null ? width : realIcon.getIconWidth(); 
   }
   // The proxy's paint method is overloaded to draw a border
   // and a message ("Loading image...") while the image is
   // loaded. After the image is loaded, it is drawn. Notice
   // that the proxy does not load the image until it is
   // actually needed.
   public void paintIcon(final Component c, 
                               Graphics g, int x, int y) {
      if(isIconCreated) {
         realIcon.paintIcon(c, g, x, y);
      }
      else {
         g.drawRect(x, y, width-1, height-1);
         g.drawString("Loading image...", x+20, y+20);
         // The image is loaded via a Runnable passed to
         // SwingUtilties.invokeLater(), which means that
         // it is executed on another thread. 
                  synchronized(this) {
            SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                  try {
                        // Slow the image-loading process.
                        Thread.currentThread().sleep(2000);
                        // ImageIcon constructor creates the image.
                        realIcon = new ImageIcon(imageName);
                        isIconCreated = true;
                  }
                  catch(InterruptedException ex) {
                        ex.printStackTrace();
                  }
                  c.repaint();
                  notifyPropertyChangeListeners();
                  }
            });
         }
      }
   }
      public void addPropertyChangeListener(
              PropertyChangeListener listener) {
            propertyChangeListeners.add(listener);
      }
      private void notifyPropertyChangeListeners() {
            for(int i=0; i < propertyChangeListeners.size(); ++i) {
                  PropertyChangeListener listener = (PropertyChangeListener)
        propertyChangeListeners.get(i);
                  listener.propertyChange(null);
            }
      }
}

As you can see from Example 5, it's not difficult to make an existing class a property change event source; at a minimum, you have to:

  • Maintain a collection of listeners
  • Implement addPropertyChangeListener()
  • Notify listeners when a bound property changes

Once you implement the above steps, you can add property change listeners to your event source and react to events.

Listen and observe

The Observer design pattern is one of the most fundamental design patterns because it allows loosely coupled objects to communicate. That ability lets you create pluggable components, which lie at the heart of object-oriented extensibility and reuse.

David Geary is the author of Core JSTL Mastering the JSP Standard Tag Library (Prentice Hall, 2002; ISBN: 0131001531), Advanced JavaServer Pages (Prentice Hall, 2001; ISBN: 0130307041), and the Graphic Java series (Prentice Hall). David has been developing object-oriented software with numerous object-oriented languages for 18 years. Since the GOF Design Patterns book was published in 1994, David has been an active proponent of design patterns, and has used and implemented design patterns in Smalltalk, C++, and Java. In 1997, David began working full-time as an author and occasional speaker and consultant. David is a member of the expert groups defining the JSP Standard Tag Library and JavaServer Faces, and is a contributor to the Apache Struts JSP framework.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies