Chapter 21. Chain of Responsibility

The Chain of Responsibility pattern allows a number of classes to attempt to handle a request without any of them knowing about the capabilities of the other classes. It provides a loose coupling between these classes; the only common link is the request that is passed between them. The request is passed along until one of the classes can handle it.

One example of such a chain pattern is a help system like the one shown in Figure 21-1. This is a simple application where different kinds of help could be useful, where every screen region of an application invites you to seek help but in which there are window background areas where more generic help is the only suitable result.

Figure 21-1. A simple application where different kinds of help could be useful

image

When you select an area for help, that visual control forwards its ID or name to the chain. Suppose you selected the New button. If the first module can handle the New button, it displays the help message. If not, it forwards the request to the next module. Eventually, the message is forwarded to an All buttons class that can display a general message about how buttons work. If there is no general button help, the message is forwarded to the general help module that tells you how the system works in general. If that doesn’t exist, the message is lost, and no information is displayed. This is illustrated in Figure 21-2.

Figure 21-2. A simple Chain of Responsibility

image

There are two significant points we can observe from this example: First, the chain is organized from most specific to most general, and second, there is no guarantee that the request will produce a response in all cases. We will see shortly that you can use the Observer pattern to provide a way for a number of classes to be notified of a change.

Applicability

The Chain of Responsibility is a good example of a pattern that helps keep knowledge separate of what each object in a program can do. In other words, it reduces the coupling between objects so that they can act independently. This also applies to the object that constitutes the main program and contains instances of the other objects. You will find this pattern helpful in the following situations.

• Several objects with similar methods are appropriate for the action the program is requesting. However, it is more appropriate for the objects to decide which one is to carry out the action than it is for you to build this decision into the calling code.

• One of the objects may be most suitable, but you don’t want to build in a series of if-else or switch statements to select a particular object.

• There might be new objects that you want to add to the possible list of processing options while the program is executing.

• There might be cases when more than one object will have to act on a request, and you don’t want to build knowledge of these interactions into the calling program.

Sample Code

The help system we just described is a little involved for a first example. Instead, let’s start with a simple visual command-interpreter program (Figure 21-3) that illustrates how the chain works. This program displays the results of typed-in commands. While this first case is constrained to keep the example code tractable, we’ll see that this Chain of Responsibility pattern is commonly used for parsers and even compilers.

Figure 21-3. A simple visual command-interpreter program that acts on one of four panels, depending on the command typed in

image

In this example, the commands can be any of the following.

• Image filenames

• General filenames

• Color names

• All other commands

In the first three cases, we can display a concrete result of the request, and in the fourth case, we can only display the request text itself.

In the preceding example system, we do the following.

  1. We type in “Mandrill” and see a display of the image Mandrill.jpg.
  2. Then we type in “File,” and that filename is displayed in the center list box.
  3. Next, we type in “blue,” and that color is displayed in the lower center panel.

Finally, if we type in anything that is neither a filename nor a color, that text is displayed in the final, right-hand list box. This is shown in Figure 21-4.

Figure 21-4. How the command chain works for the program in Figure 21-3

image

To write this simple Chain of Responsibility program, we start with an abstract Chain class.


public abstract class Chain      {
      //describes how all chains work
      private bool hasLink;
      protected Chain chn;
      public Chain()     {
             hasLink = false;
      }
      //you must implement this in derived classes
      public abstract void sendToChain(string mesg);
      //-----
      public void addToChain(Chain c) {
      //add new element to chain
             chn = c;
             hasLink = true;     //flag existence
      }
      //-----
      public Chain getChain() {
             return chn;         //get the chain link
      }
      //-----
      public bool hasChain() {
             return hasLink;     //true if linked to another
      }
      //-----
      protected void sendChain(string mesg) {
      //send message on down the chain
             if(chn != null)
                    chn.sendToChain (mesg);
      }
}

The addToChain method adds another class to the chain of classes. The getChain method returns the current class to which messages are being forwarded. These two methods allow us to modify the chain dynamically and add additional classes in the middle of an existing chain. The sendToChain method forwards a message to the next object in the chain. And the protected sendChain method only sends the message down the chain if the next link is not null.

Our main program assembles the Chain classes and sets a reference to a control into each of them. We start with the ImageChain class, which takes the message string and looks for a .jpg file of that name. If it finds one, it displays it in the Image control, and if not, it sends the command on to the next element in the chain.


public class ImageChain :Chain   {
      PictureBox picBox;         //image goes here
      //-----
      public ImageChain(PictureBox pc)         {
             picBox = pc;        //save reference
      }
      //-----
      public override void sendToChain(string mesg) {
             //put image in picture box
             string fname = mesg + ".jpg";
             //assume jpg filename
             csFile fl = new csFile(fname);
             if(fl.exists())
                    picBox.Image = new Bitmap(fname);
             else{
                    if (hasChain()){    //send off down chain
                           chn.sendToChain(mesg);
                 }
             }
      }
}

In a similar fashion, the ColorChain class simply interprets the message as a color name and displays it if it can. This example only interprets three colors, but you could implement any number. Note how we interpret the color names by using them as keys to a Hashtable of color objects where the string names are the keys.


public class ColorChain : Chain {
      private Hashtable colHash; //color list kept here
      private Panel panel;              //color goes here
      //-----
      public ColorChain(Panel pnl)             {
             panel = pnl;               //save reference
             //create Hash table to correlate color names
             //with actual Color objects
             colHash = new Hashtable ();
             colHash.Add ("red", Color.Red);
             colHash.Add ("green", Color.Green);
             colHash.Add ("blue", Color.Blue);
      }
      //-----
      public override void sendToChain(string mesg) {
             mesg = mesg.ToLower ();
             try {
                    Color c = (Color)colHash[mesg];
                    //if this is a color, put it in the panel
                    panel.BackColor =c;
             }
             catch (NullReferenceException e) {
                    //send on if this doesn't work
                    sendChain(mesg);
             }
      }
}

ListBoxes

Both the file list and the list of unrecognized commands are ListBoxes. If the message matches part of a filename, the filename is displayed in the fileList box, and if not, the message is sent on to the NoCmd Chain element.


public override void sendToChain( string mesg) {
      //if the string matches any part of a filename
      //put those filenames in the file list box
      string[] files;
      string fname = mesg + "*.*";
      files = Directory.GetFiles(
             Directory.GetCurrentDirectory(), fname);
      //add them all to the listbox
      if (files.Length > 0){
             for (int i = 0; i< files.Length; i++) {
                    csFile vbf = new csFile(files[i]);
                    flist.Items.Add(vbf.getRootName());
             }
      }
      else {
             if ( hasChain()) {
                    chn.sendToChain(mesg);
             }
      }
}

The NoCmd Chain class is very similar. It, however, has no class to which to send data.


public class NoCmd :Chain {
      private ListBox lsNocmd;  //commands go here
      //-----
      public NoCmd(ListBox lb)         {
             lsNocmd = lb;             //copy reference
      }
      //-----
      public override void sendToChain(string mesg) {
             //adds unknown commands to list box
             lsNocmd.Items.Add (mesg);
       }
}

Finally, we link these classes together in the Form_Load routine to create the Chain.


private void init() {
      //set up chains
      ColorChain clrChain = new ColorChain(pnlColor);
      FileChain flChain = new FileChain(lsFiles);
      NoCmd noChain = new NoCmd(lsNocmd);
      //create chain links
      chn = new ImageChain(picImage);
      chn.addToChain(clrChain);
      clrChain.addToChain(flChain);
      flChain.addToChain(noChain);
}

Finally, we kick off the Chain by clicking on the Send button, which takes the current message in the text box and sends it along the chain.


private void btSend_Click(object sender, EventArgs e) {
                    chn.sendToChain (txCommand.Text );
}

You can see the relationship between these classes in the UML diagram in Figure 21-5.

Figure 21-5. The class structure of the Chain of Responsibility program

image

The Sender class is the initial class that implements the Chain interface. It receives the button clicks and obtains the text from the text field. It passes the command on to the Imager class, the FileList class, the ColorImage class, and finally to the NoCmd class.

Programming a Help System

As we noted at the beginning of this discussion, help systems provide good examples of how the Chain of Responsibility pattern can be used. Now that we’ve outlined a way to write such chains, we’ll consider a help system for a window with several controls. The program (Figure 21-6) pops up a help dialog message when the user presses the F1 (help) key. The message depends on which control is selected when the F1 key is pressed.

Figure 21-6. A simple help demonstration

image

In the preceding example, the user has selected the Quit key, which does not have a specific help message associated with it. Instead, the chain forwards the help request to a general button help object that displays the message shown on the right.

To write this help chain system, we begin with an abstract Chain class that has handled Controls instead of messages. Note that no message is passed into the sendToChain method and that the current control is stored in the class.


public abstract class Chain      {
      //describes how all chains work
      private bool hasLink;
      protected Control control;
      protected Chain chn;
      protected string message;

      public Chain(Control c, string mesg)    {
             hasLink = false;
             control = c;        //save the control
             message = mesg;
      }

      public abstract void sendToChain();
      //-----
      public void addToChain(Chain c) {
             //add new element to chain
             chn = c;
             hasLink = true;           //flag existence
      }
      //-----
      public Chain getChain() {
             return chn;  //get the chain link
      }
      //-----
      public bool hasChain() {
             return hasLink;           //true if linked to nother
             }
      //-----
      protected void sendChain() {
      //send message on down the chain
             if(chn != null)
                    chn.sendToChain ();
      }
}

Then you might create specific classes for each of the help message categories you want to produce. As we illustrated earlier, we want help messages for the following.

• The New button

• The File button

• A general button

• A general visual control (covering the check boxes)

In C#, one control will always have the focus, and thus we don’t really need a case for the Window itself. However, we’ll include one for completeness. However, there is little to be gained by creating separate classes for each message and assigning different controls to them. Instead, we’ll create a general ControlChain class and pass in the control and the message. Then within the class it checks to see if that control has the focus, and if it does, it issues the associated help message.


public class ControlChain:Chain  {
      public ControlChain(Control c, string mesg):base(c, mesg){}
      public override void sendToChain() {
             //if it has the focus display the message
             if (control.Focused ) {
                    MessageBox.Show (message);
             }
             else
                    //otherwise pass on down the chain
                    sendChain();
      }
}

Finally, we need one special case: the end of Chain that will display a message regardless of whether the control has the focus. This is the EndChain class, and it is for completeness. Since one of the controls will presumably always have the focus, it is unlikely that it will ever be called.


public class EndChain:Chain      {
      public EndChain(Control c, string mesg):base(c, mesg){}
      //default message display class
      public override void sendToChain() {
             MessageBox.Show (message);
      }
}

We construct the Chain in the form initializer as follows.


chn = new ControlChain(btNew, "Create new files");
Chain fl =new ControlChain (btFile, "Select a file");
chn.addToChain (fl);
Chain bq = new ControlChain (btQuit, "Exit from program");
fl.addToChain (bq);
Chain cb =new ControlChain (ckBinary, "Use binary files");
bq.addToChain (cb);
Chain ct =  new ControlChain (ckText, "Use text files");
cb.addToChain (ct);
Chain ce = new EndChain (this, "General message");
ct.addToChain (ce);

Receiving the Help Command

Now we need to assign keyboard listeners to look for the F1 keypress. At first, you might think we need five such listeners—for the three buttons and the two check boxes. However, we can simply make a single KeyDown event listener and assign it to each of the controls.


KeyEventHandler keyev =  new KeyEventHandler(Form1_KeyDown);
      btNew.KeyDown += keyev;
      btFile.KeyDown += keyev;
      btQuit.KeyDown += keyev;
      ckBinary.KeyDown += keyev;
      ckText.KeyDown += keyev;

Then, of course, the KeyDown event launches the Chain if the F1 key is pressed.


private void Form1_KeyDown(object sender, KeyEventArgs e) {
      if(e.KeyCode  == Keys.F1 )
             chn.sendToChain ();
}

We show the complete class diagram for this help system in Figure 21-7.

Figure 21-7. The class diagram for the help system

image

A Chain or a Tree?

Of course, a Chain of Responsibility does not have to be linear. The Smalltalk Companion suggests that it is more generally a tree structure with a number of specific entry points all pointing upward to the most general node, as shown in Figure 21-8.

Figure 21-8. The Chain of Responsibility implemented as a tree structure

image

However, this sort of structure seems to imply that each button, or its handler, knows where to enter the Chain. This can complicate the design in some cases and may preclude the need for the Chain at all.

Another way of handling a treelike structure is to have a single entry point that branches to the specific button, menu, or other widget types and then “unbranches,” as previously, to more general help cases. There is little reason for this complexity—you could align the classes into a single Chain, starting at the bottom, and going left to right and up a row at a time until the entire system had been traversed, as shown in Figure 21-9.

Figure 21-9. The same Chain of Responsibility implemented as a linear Chain

image

Kinds of Requests

The request or message passed along the Chain of Responsibility may well be a great deal more complicated than just the string or Control that we conveniently used on these examples. The information could include various data types or a complete object with a number of methods. Since various classes along the Chain may use different properties of such a request object, you might end up designing an abstract Request type and any number of derived classes with additional methods.

Examples in C#

Under the covers, C# form windows receive various events, such as MouseMove, and then forward them to the controls the form contains. However, only the final control ever receives the message in C#, whereas in some other languages, each containing control does as well. This is a clear implementation of the Chain of Responsibility pattern. We could also argue that, in general, the C# class inheritance structure itself exemplifies this pattern. If you call for a method to be executed in a deeply derived class, that method is passed up the inheritance Chain until the first parent class containing that method is found. The fact that further parents contain other implementations of that method does not come into play.

We will also see that the Chain of Responsibility is ideal for implementing Interpreters, and we use one in the Interpreter pattern we discuss later.

The Chain of Responsibility

The main purpose for this pattern, like many others, is to reduce coupling between objects. An object only needs to know how to forward the request to other objects.

Each C# object in the chain is self-contained. It knows nothing of the others and only need decide whether it can satisfy the request. This makes both writing each one and constructing the chain very easy.

You can decide whether the final object in the chain handles all requests it receives in some default fashion or just discards them. However, you do have to know which object will be last in the chain for this to be effective.

Finally, since C# cannot provide multiple inheritance, the basic Chain class sometimes needs to be an interface rather than an abstract class so the individual objects can inherit from another useful hierarchy, as we did here by deriving them all from Control. The disadvantage of this approach is that you often have to implement the linking, sending, and forwarding code in each module separately or, as we did here, by subclassing a concrete class that implements the Chain interface.

Thought Question

Suggest how you might use a Chain of Responsibility to implement an e-mail filter.

Programs 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