Chapter 22. The Observer Pattern

In this chapter we discuss how you can use the Observer pattern to present data in several forms at once.

In the more sophisticated windowing world, we often want to display data in more than one form at the same time and have all of the displays reflect any changes in the data. For example, we might represent stock price changes as both a graph and a table or list box. Each time the price changes, we'd expect both representations to change at once without any action on our part.

We expect this sort of behavior because many Windows applications, such as Excel, demonstrate such behavior. However, nothing inherent in Windows allows this activity, and, as you might know, programming directly in Windows in C or C++ is quite complicated. In Java, however, you can easily use the Observer pattern to cause your program to behave in this way.

The Observer pattern assumes that the object containing the data is separate from the objects that display the data and that these data objects observe changes in those data. This idea is illustrated in Figure 22.1.

Data are displayed as a list and in some graphical mode.

Figure 22.1. Data are displayed as a list and in some graphical mode.

When we implement the Observer pattern, we usually refer to the data as the Subject and each of the displays as Observers. Each Observer registers its interest in the data by calling a public method in the Subject, and each has a known interface that the Subject calls when the data change. We could define these interfaces as follows:

public interface Observer {
    //notify Observers that a change has taken place
    public void sendNotify(String s);
}
public interface Subject {
//tell the Subject that you are interested in changes
    public void registerInterest(Observer obs);
}

The advantages of defining these abstract interfaces are that you can write any sort of class objects you want as long as they implement these interfaces, and that you can declare these objects to be of type Subject and Observer no matter what else they do.

Watching Colors Change

Let's write a program to illustrate how to use this powerful concept. It shows a display frame containing three radio buttons named Red, Blue, and Green, as shown in Figure 22.2.

A simple control panel to create red, green, or blue "data."

Figure 22.2. A simple control panel to create red, green, or blue "data."

This main window is the Subject, or data repository object. We create this window using the JFC in the following code:

public class Watch2Windows  extends JxFrame
implements ActionListener,  ItemListener, Subject {
    private JButton         Close;
    private JRadioButton    red, green, blue;
    private Vector          observers;
    private ColorFrame      cframe;
    private ListFrame       lframe;
//--------------
    public Watch2Windows() {
        super("Change 2 other frames");
        observers = new Vector();     //list of observing frames

        JPanel p = new JPanel(true);  //add a panel to the content
                                        pane
        p.setLayout(new BorderLayout());
        getContentPane().add("Center", p);

        Box box = new Box(BoxLayout.Y_AXIS);  //vertical box layout
        p.add("Center", box);
        box.add(red = new JRadioButton("Red"));  //and three radio
                                                   buttons
        box.add(green = new JRadioButton("Green"));
        box.add(blue = new JRadioButton("Blue"));

        blue.addItemListener(this);   //listen for clicks
        red.addItemListener(this);     //on the radio buttons
        green.addItemListener(this);

        //make all part of same button group
        ButtonGroup bgr = new ButtonGroup();
        bgr.add(red);
        bgr.add(green);
        bgr.add(blue);

        //put a Close button at the bottom of the frame
        JPanel p1 = new JPanel();
        p.add("South", p1);
        pl.add(Close =new JButton("Close"));
        Close.addActionListener(this):        //listen for clicks
        setBounds(200, 200, 200, 200);
        pack();

}

Note that the main frame class implements the Subject interface. Thus it must provide a public method for registering interest in the data in this class. This method is registerInterest, which just adds Observer objects to a Vector.

public void registerInterest(Observer obs) {
        //adds an Observer to the list
        observers.addElement(obs);
    }

Next we create two Observers, one that displays the color (and its name) and another that adds the current color to a list box.

//-----create Observers-----
    ColorFrame cframe = new ColorFrame(this);
    ListFrame lframe = new ListFrame(this);
    setVisible(true);

When we create the ColorFrame window, we register out interest in the data in the main program.

public class ColorFrame extends Jframe
implements Observer {
    private Color color;
    private String color_name="black";
    private Font font;

    private JPanel p = new JPanel(true);
    public ColorFrame(Subject s) {
        super("Colors");
        getContentPane().add("Center", p);
        s.registerInterest(this);
        setBounds(100, 100, 100, 100);
        font = new Font("Sans Serif", Font.BOLD, 14);
        setVisible(true);
    }
    public void sendNotify(String s) {
            color_name = s;
            if (s.equalsIgnoreCase ("RED"))
                color = Color.red;
            if (s.equalsIgnoreCase ("BLUE"))
                color = Color.blue;
            if (s.equalsIgnoreCase ("GREEN"))
                color = Color.green;
            //p.repaint();
            setBackground(color);
        }
        public void paint(Graphics g) {

            g.setFont(font);
            g.drawString(color_name, 20, 50);
    }
}

Meanwhile, in the main program, every time someone clicks on one of the radio buttons, it calls the sendNotify method of each Observer that registered interest in such a change by simply running through the objects in the Observer's Vector.

public void itemStateChanged(ItemEvent e) {
    //responds to radio button clicks
    //if the button is selected
    if (e.getStateChange() == ItemEvent.SELECTED)
            notifyObservers((JRadioButton)e.getSource());
}
//--------------
private void notifyObservers(JRadioButton rad) {
    //sends text of selected button to all observers
    String color = rad.getText();
    for (int i = 0; i< observers.size(); i++) {
        ((Observer)(observers.elementAt(i))).sendNotify(color);
    }
}

In the case of the ColorFrame Observer, the sendNotify method changes thebackground color and the text string in the Frame panel. In the case of the ListFrame Observer, it just adds the name of the new color to the list box. Figure 22.3 shows the final program running.

The data control pane generates data, which are displayed simultaneously as a colored panel and as a list box. This is a candidate for an Observer pattern.

Figure 22.3. The data control pane generates data, which are displayed simultaneously as a colored panel and as a list box. This is a candidate for an Observer pattern.

The Message to the Media

Now, what kind of notification should a Subject send to its Observers? In this carefully circumscribed example, the notification message is the string representing the color itself. When we click on one of the radio buttons, we can get the caption for that button and send it to the Observers. This, of course, assumes that all of the Observers can handle that string representation. In more realistic situations, this might not always be the case, especially if the Observers could also be used to observe other data objects. Here, we undertake two simple data conversions:

  1. We get the label from the radio button and send it to the Observers.

  2. We convert the label to an actual color in the ColorFrame Observer.

A more complicated system might have Observers that demand specific, but different, kinds of data. Rather than have each Observer convert the message to the right data type, we could use an intermediate Adapter class to perform this conversion.

Another problem that observers might have to deal with is the case where the data of the central Subject class can change in several ways. We could delete points from a list of data, edit their values, or change the scale of the data that we are viewing. In these cases, we must either send different change messages to the Observers or send a single message and then have the Observer ask which sort of change has occurred.

The JList as an Observer

Now, what about that list box in our color changing example? We saved it for last because, as we noted in the Adapter class discussion (Chapter 9), the JList is rather different in concept than the List object in the AWT. You can display a fixed list of data in the JList by simply putting the data into a Vector or a String array. However, to display a list of data that might grow or otherwise change, you must put that data into a special data object derived from the AbstractListModel class and then use that class in the constructor to the JList class. The ListFrame class looks like this:

public class ListFrame extends Jframe
implements Observer {
    private JList          list;
    private  JPanel        p;
    private JScrollPane    lsp;
    private JListData      listData;

    public ListFrame(Subject s) {
        super("Color List");
        //put the panel into the frame
        p = new JPanel(true);
        getContentPane().add("Center", p);
        p.setLayout(new BorderLayout());
        //tell the Subject that we are interested
        s.registerInterest(this);

        //Create the list
        listData = new JListData();    //the list model
        list = new JList(listData);    //the visual list
        lsp = new JScrollPane();       //the scroller
        lsp.getViewport().add(list);
        p.add("Center", lsp);
        lsp.setPreferredSize(new Dimension(100,100));
        setBounds(250, 100, 100, 100);
        setVisible(true);
    }
    //--------------
    public void sendNotify(String s) {
        listData.addElement(s);
    }
}

We name the ListModel class JListData. It holds the Vector that contains the growing list of color names.

public class JListData extends AbstractListModel {
    private Vector data;
    public JListData() {
        data = new Vector();
    }
    public int getSize() {
        return data.size();
    }
    public Object getElementAt(int index) {
        return data.elementAt(index);
    }
    public void addElement(String s) {
        data.addElement(s);
        fireIntervalAdded(this, data.size()-1, data.size());
    }
}

Whenever the ColorList class is notified that the color has changed, it calls the addElement method of the JListData class. This method adds the string to theVector and then calls the fireIntervalAdded method. This base method of the AbstractListModel class connects to the JList class, telling that class that thedata has changed. The JList class then redisplays the data as needed. There are also equivalent methods for two other kinds of changes: fireIntervalRemoved and fireContentsChanged. These represent the three kinds of changes that can occur in a list box; here, each sends its own message to the JList display.

The class diagram in Figure 22.4 clearly shows the Observer pattern. It also illustrates the Observer relationship between the ColorList class and its JListdata observer.

The Observer interface and Subject interface implementation of the Observer pattern.

Figure 22.4. The Observer interface and Subject interface implementation of the Observer pattern.

The MVC Architecture as an Observer

As noted in Section 5, the JList, JTable, and JTree objects all operate as Observers of a data model. In fact, all of the visual components derived from JComponent can have this same division of labor between the data and the visual representation. In JFC parlance, this is called the MVC architecture (Model-View-Controller), whereby the data are represented by the Model and the View by the visual component. The Controller is the communication between the Model and View objects and may be a separate class or may be inherent in either the Model or the View. This is the case for the JFC components, which are all examples of the Observer pattern.

The Observer Interface and Observable Class

Java has an Observer interface built into the java.util package that has one method:

public void update(Observable o, Object arg)

This method is intended to be called whenever the observed object changes. The second argument can be used to describe the nature of the change.

Unfortunately, Observable is implemented as a class rather than an interface, so you can use it only when your base class is not already determined. However, since this is rarely the case in GUI applications, the Observable class is seldom used. On the other hand, you could easily use this Observer interface instead of the simpler one developed in this chapter.

Consequences of the Observer Pattern

The Observer pattern promotes abstract coupling to Subjects. A Subject doesn't know the details of any of its Observers. This has the potential disadvantage of successive or repeated updates to the Observers when there is a series of incremental changes to the data. If the cost of these updates is high, introducing some sort of change management might be necessary so that the Observers are not notified too soon or too often.

When one client changes the underlying data, you need to decide which object will initiate the notification of the change to the other Observers. If the Subject notifies all of the Observers when it is changed, each client is not responsible for remembering to initiate the notification. However, this can result in a number of small successive updates being triggered. If the clients tell the Subject when to notify the other clients, this cascading notification can be avoided; however, the clients are left with the responsibility to tell the Subject when to send the notifications. If one client "forgets," the program simply won't work properly.

Finally, you can specify the kind of notification to send by defining a number of update methods for the Observers to receive, depending on the type or scope of change. In some cases, the clients will thus be able to ignore some of these notifications.

Thought Questions

  1. Rewrite the Watch2Windows program in this chapter to use the java.util.Observer interface.

  2. Write a program using the Observable class and Observable interface.

Programs on the CD-ROM

Program Description

ObserverWatch2Windows.java

Illustrates the Observer pattern between a main window and two observing windows.

ObserverLtestLtest.java

A simple example of how a JList observes a JListData object.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset