Chapter 27. The Observer Pattern

In this chapter we discuss how you can use the Observer pattern to present data in several forms at once. In our new, more sophisticated windowing world, we often would like to display data in more than one form at the same time and have all of the displays reflect any changes in that data. For example, you might represent stock price changes both as a graph and as 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 there are any number of Windows applications, like Excel, where we see that behavior. Now there is nothing inherent in Windows to allow this activity, and as you may know, programming directly in Windows in C or C++ is pretty complicated. In C#, however, we can easily use the Observer design pattern to make our program behave this way.

The Observer pattern assumes that the object containing the data is separate from the objects that display the data and that these display objects observe changes in that data. This is simple to illustrate, as we see in Figure 27-1.

Figure 27-1. Data are displayed as a list and in some graphical mode.

image

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


public interface Observer {
      void sendNotify(string message);
//-----
public interface Subject  {
      void registerInterest(Observer obs);
}

The advantages of defining these abstract interfaces are (1) you can write any sort of class objects you want as long as they implement these interfaces, and (2) 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 simple program to illustrate how we can use this powerful concept. Our program shows a display form containing three radio buttons named Red, Blue, and Green, as shown in Figure 27-2.

Figure 27-2. A simple control panel to create red, green, or blue “data”

image

Now our main form class implements the Subject interface. That means it must provide a public method for registering interest in the data in this class. This method is the registerInterest method, which just adds Observer objects to an ArrayList.


public void registerInterest(Observer obs ) {
      observers.Add (obs);
}

Now we create two observers: one that displays the color (and its name) and one that adds the current color to a list box. Each of these is actually a Windows form that also implements the Observer interface. When we create instances of these forms, we pass to them the base or startup form as an argument. Since this startup form is actually the Subject, they can register their interest in its events. So the main form’s initialization creates these instances and passes them a reference to itself.


ListObs lobs = new ListObs (this);
lobs.Show ();
ColObserver colObs = new ColObserver (this);
colObs.Show();

Then when we create our ListObs window, we register our interest in the data in the main program.


public ListObs(Subject subj)            {
      InitializeComponent();
      init(subj);
}
//------
public void init(Subject subj) {
      subj.registerInterest (this);
}

When it receives a sendNotify message from the main subject program, all it has to do is add the color name to the list.


public void sendNotify(string message){
      lsColors.Items.Add(message);
}

Our color window is also an observer, and it has to change the background color of the picture box and paint the color name using a brush. Note that we change the picture box’s background color in the sendNotify event and change the text in a paint event. The entire class is shown here.


public class ColObserver : Form, Observer{
      private Container components = null;
      private Brush bBrush;
      private System.Windows.Forms.PictureBox pic;
      private Font fnt;
      private Hashtable colors;
      private string colName;
      //-----
      public ColObserver(Subject subj)        {
             InitializeComponent();
             init(subj);
      }
      //-----
      private void init(Subject subj) {
             subj.registerInterest (this);
             fnt = new Font("arial", 18, FontStyle.Bold);
             bBrush = new SolidBrush(Color.Black);
             pic.Paint+= new PaintEventHandler (paintHandler);
             //make Hashtable for converting color strings
             colors = new Hashtable ();
             colors.Add("red", Color.Red );
             colors.Add ("blue", Color.Blue );
             colors.Add ("green", Color.Green );
             colName = "";
      }
      //-----
      public void sendNotify(string message) {
             colName = message;
             message = message.ToLower ();
             //convert color string to color object
             Color col = (Color)colors[message];
             pic.BackColor = col;
      }
      //-----
      private void paintHandler(object sender,
                    PaintEventArgs e) {
             Graphics g = e.Graphics ;
             g.DrawString(colName, fnt, bBrush, 20, 40)
      }

Note that our sendNotify event receives a string representing the color name and that we use a Hashtable to convert these strings to actual Color objects.

Meanwhile, in our main program, every time someone clicks on one of the radio buttons, it calls the sendNotify method of each Observer who has registered interest in these changes by simply running through the objects in the Observer’s Collection.


private void opButton_Click(object sender, EventArgs e) {
      RadioButton but = (RadioButton)sender;
      for(int i=0; i< observers.Count ; i++ ) {
             Observer obs = (Observer)observers[i];
             obs.sendNotify (but.Text );
      }
}

In the case of the ColorForm observer, the sendNotify method changes the background color and the text string in the form Picturebox. In the case of the ListForm observer, however, it just adds the name of the new color to the list box. We see the final program running in Figure 27-3 and a UML representation of the interfaces as in Figure 27-4.

Figure 27-3. The data control panel generates data that is displayed simultaneously as a colored panel and as a list box.This is a candidate for an Observer pattern.

image

Figure 27-4. The observer interface and subject interface implementation of the Observer pattern

image

The Message to the Media

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 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 ColObserver.

In more complicated systems, we 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 observers may 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 we are viewing. In these cases we either need to send different change messages to the observers or send a single message and then have the observer ask which sort of change has occurred.

Consequences of the Observer Pattern

Observers promote abstract coupling to subjects. A subject doesn’t know the details of any of its observers. However, this has the potential disadvantage of successive or repeated updates to the observers when there are a series of incremental changes to the data. If the cost of these updates is high, it may be necessary to introduce some sort of change management so the observers are not notified too soon or too frequently.

When one client makes a change in 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 the observers when it is changed, each client is not responsible for remembering to initiate the notification. On the other hand, 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, but the clients are left with the responsibility of telling 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 you choose 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.

Program on the CD-ROM

image

..................Content has been hidden....................

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