Chapter 16. Chain of Responsibility Pattern

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 pattern is a Help system like the one shown in Figure 16.1. Here, every screen region of an application invites you to seek help, but in some window background areas, more generic help is the only suitable result.

A simple application where different screen areas provide different help messages.

Figure 16.1. A simple application where different screen areas provide different help messages.

When you select an area for help, that visual control forwards its ID or name to the chain. Suppose that you selected the New button. If the first module can handle the New button, it displays the help message; otherwise, 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 no general button help is available, the message is forwarded to the general help module that tells you how the system works. If that doesn't exist, the message is lost, and no information is displayed. This is illustrated in Figure 16.2.

A simple Chain of Responsibility pattern.

Figure 16.2. A simple Chain of Responsibility pattern.

There are two significant points we can observe from this example:

  1. The chain is organized from most specific to most general.

  2. There is no guarantee that the request will produce a response in all cases.

We will see later that the Observer pattern defines how multiple classes can be notified of a change.

Applicability

The Chain of Responsibility is a good example of a pattern that helps to keep separate the knowledge of what each object in a program can do. That is, 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 when

  • Several objects have similar methods that could be appropriate for the action that 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 might 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 list of processing options while the program is executing.

  • Sometimes 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 described previously is a little involved for a first example. So let's start with a simple visual command-interpreter program, shown in Figure 16.3, that illustrates how the chain works. This program displays the results of typed commands. While this first case is constrained to keep the example code tractable, we'll see that this Chain or Responsibility pattern is commonly used for parsers and even compilers.

A simple visual command-interpreter program that acts on one of four panels, depending on the command you type in.

Figure 16.3. A simple visual command-interpreter program that acts on one of four panels, depending on the command you type in.

In this example, the commands may be any of the following:

  • Image filenames

  • General filenames

  • Color names

  • Any other commands

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

This system responds to commands as follows:

  1. We type in "Mandrill," and see a display of the image Mandrill.jpg.

  2. We type in "FileList," and that filename is highlighted in the center list box.

  3. We type in "blue," and that color is displayed in the lower center panel.

If we type in anything that is neither a filename nor a color, that text is displayed in the right-hand list box.

This sequence of steps is shown in Figure 16.4.

The command chain for the program in Figure 16.3.

Figure 16.4. The command chain for the program in Figure 16.3.

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

public interface Chain {
public abstract void addChain(Chain c);
public abstract void sendToChain(String mesg);
public abstract Chain getChain();
)

The addChain 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.

The Imager class is derived from JPanel and implements the Chain interface. It takes the message and looks for .jpg files with that root name. If it finds one, it displays it.

public class Imager extends JPanel implements Chain {
    private Chain    nextChain;
    private Image    img;
    private boolean loaded;

    public Imager(){
        super();
        loaded = false;
        setBorder(new BevelBorder(BevelBorder.RAISED));
    }
//--------------
    public void addChain(Chain c) {
        nextChain = c;    //next in chain of resp
    }
//--------------
    public void sendToChain(String mesg) {
        //if there is a JPEG file with this root name
        //load it and display it.
        if (findImage(mesg))
            loadImage(mesg+".jpg");
        else
            //Otherwise, pass request along chain
            nextChain.sentToChain(mesg);
    }
//--------------
    public Chain getChain() {
        return nextChain;
    }
//--------------
    public void paint(Graphics g) {
        if (loaded) {
            g.drawImage(img, 0, 0, this);
        }
}

Similarly, the ColorImage class simply interprets the message as a color name and displays it if it can. This example interprets only three colors, but could implement any number.

public void sendToChain(String mesg) {
        Color c = getColor(mesg);
        if (c != null) {
            setBackground(c);
            repaint();
        } else {
            if (nextChain != null)
                nextChain.sentToChain(mesg);
        }
    }
//--------------
    private Color getColor(String mesg) {
        String lmesg = mesg.toLowerCase();
        Color c = null;

        if (lmesg.equals("red"))
            c = Color.red;
        if (lmesg.equals("blue"))
            c = Color.blue;
        if (lmesg.equals("green"))
            c = Color.green;
        return c;
    }

The List Boxes

Both the file list and the list of unrecognized commands are JList boxes. Since we developed an adapter JawtList in the previous chapter to give JList a simpler interface, we'll use that adapter here. The RestList class is the end of the chain, and any command that reaches it is simply displayed in the list. However, to allow for convenient extension, we are able to forward the message to other classes as well.

public class RestList extends JawtList
implements Chain {
    private Chain nextChain = null;
    public RestList() {
        super(10);        //arg to JawtList
        setBorder(new LineBorder(Color.black));
    }
    public void addChain(Chain c) {
        nextChain = c;
    }
    public void sendToChain(String mesg){
        add(mesg);        //the end of the chain
        repaint();
        if (nextChain != null)
            nextChain.sendToChain(mesg);
    }
    public Chain getChain() {
        return nextChain;
    }
}

The FileList class is quite similar and can be derived from the RestList class to avoid replicating the addChain and getChain methods. It differs only in that it loads a list of the files in the current directory into the list when initialized and looks for one of those files when it receives a request.

public class FileList extends RestList {
    String files[];
    private Chain nextChain;

    public FileList() {
        super();
        setBorder(new CompoundBorder (new EmptyBorder(5,5,5,5),
            new LineBorder(Color.black)));
        String tmp = "";
        File dir = new File(System.getProperty("user.dir"));
        files = dir.list();
        for (int i = 0; i < files.length; i++) {
            for (int j = i; j < files.length; j++) {
                if (files[i].toLowerCase().compareTo(
                        files[j].toLowerCase()) > 0) {
                    tmp = files[i];
                    files[i] = files[j];
                    files[j] = tmp;
                }
            }
        }
        for (int i = 0; i<files.length; i++)
            add(files[i]);
    }
//--------------
    public void sendToChain(String mesg) {

        boolean found = false;
        int i = 0;
        while ((! found) && (i < files.length)) {
            XFile xfile = new XFile(files[i]);
            found = xfile.matchRoot(mesg);
            if (! found) i++;
        }
        if (found) {
            setSelectedIndex(i);
        } else {
            if (nextChain != null)
                nextChain.sendToChain(mesg);
        }
    }

The Xfile class we introduce above is a simple child of the File class that contains a matchRoot method to compare a string to the root name of a file. Finally, we link these classes together in the constructor to form the Chain.

//set up the chain of responsibility
    sender.addChain(imager);
    imager.addChain(colorImage);
    colorImage.addChain(fileList);
    fileList.addChain(restList);

You can see the relationship between these classes in the UML diagram in Figure 16.5.

The class structure of the Chain of Responsibility program.

Figure 16.5. The class structure of the Chain of Responsibility program.

The Sender class is the initial class that implements the Chain interface. It receives the button clicks, obtains the text from the text field, and passes the command on to the Imager class, the FileList class, the ColorImage class, and finally to the RestList class. Note that FileList is a subclass of RestList and implements the Chain interface because the parent RestList class does.

Programming a Help System

As we noted earlier in this discussion, help systems provide good examples of how the Chain or Responsibility pattern can be used. Now that we've outlined a way to write such chains, let's consider a help system for a window that has several controls. The program, shown in Figure 16.6, pops up a help dialog message when the user presses the F1 (help) key. Which message is displayed depends on which control is selected when the F1 key is pressed.

A simple help demonstration that pops up a different message, depending on which control is selected when the F1 key is pressed.

Figure 16.6. A simple help demonstration that pops up a different message, depending on which control is selected when the F1 key is pressed.

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

To write this help chain system, we begin with a general concrete HelpWindow class that has empty implementations of three of the Chain interface methods. The class does, however, check for whether the next item in the chain is non-null before forwarding the message to that item.

public class HelpWindow implements Chain {
    protected Chain nextChain;
    protected String mesg;

    public HelpWindow() {
        mesg ="";
    }
//--------------
    public void addChain(Chain c) {
        nextChain = c;
    }
    public Chain getChain() {
        return this;
    }
    public void sendToChain(Component c) {
        sendChain(c);
    }
//--------------
    protected void sendChain (Component c) {
        if (nextChain != null) {
            nextChain.sendToChain(c);
        }
    }
}

Then we derive specific subclasses for each of the help message categories that we want to produce. In this case, we want help messages for

  • The New button

  • The File button

  • A general button

  • A general visual control (covering the check boxes)

  • The window itself

So we write classes for these and combine them into a chain as follows:

    chain = new FileNewHelp(this);
    FileHelp fhelp = new FileHelp(this);
    chain.addChain (fhelp);
    ButtonHelp bhelp = new ButtonHelp(this);
    fhelp.addChain(bhelp);
    ControlHelp chelp = new ControlHelp(this);
    bhelp.addChain (chelp);
    WindowHelp whelp = new WindowHelp(this);
    chelp.addChain (this);

Receiving the Help Command

Next, we need to assign keyboard listeners to look for the F1 keypress. You might think that we need six such listeners: one each for the three buttons, the two check boxes, and the background window. However, we really need only one listener—for the Frame window itself. This is because the Java Swing event dispatcher forwards these events to all surrounding (parent) components. In other words, the keyPress event is received successively by

  1. the selected component,

  2. the Panel containing the component, and

  3. the Frame.

This is in fact an interval Java implementation of the Chain of Responsibility, in which the request is the keyPressed event. So in constructing the event listener in this program's user interface, we need only to define a subclass of KeyAdapter that traps the F1 key

//    inner class for key strokes
    class k_adapter extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            if(e.getKeyCode () == KeyEvent.VK_F1) {
                sendToChain((JComponent)getFocusOwner());
            }
        }
    }

and add a key event listener to the Frame

    helpKey = new key_adapter();
    addKeyListener(helpKey);

Note that we obtain the component that has the current focus by using getFocusOwner and then send that component along the chain to obtain the most specific help message that the system provides. Each help object has a test to check whether the object is the one described by that help message. Either the message is displayed or the object is forwarded to the next chain element. For the File button, the class looks like the following:

public class FileHelp extends HelpWindow {
    String message, title;
    int icon;
    JFrame frame;

    public FileHelp(JFrame f) {
        mesg = "Select to open a file";
        title ="File button help";
        icon = JOptionPane.DEFAULT_OPTION;
        frame = f;
    }
    public void sendToChain(JComponent c) {
        if (c instanceof JButton) {
            JButton b = (JButton)c;
            if( b.getText().equals("File"))
                JOptionPane.showMessageDialog(frame, mesg,
                                              title, icon);
            else
                sendChain(c);
        }
    }
}

Figure 16.7 shows the complete class diagram for this help system.

The class diagram for the help system.

Figure 16.7. The class diagram for the help system.

A Chain or a Tree?

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

The Chain of Responsibility implemented as a tree structure.

Figure 16.8. The Chain of Responsibility implemented as a tree structure.

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 tree-like structure is to have a single-entry point that branches to the specific button, menu, or other widget type and then "unbranches" as described previously to more general help cases. There is little reason for that complexity. We could instead align the classes into a single chain, starting at the bottom, and proceed left to right and up a row at a time until the entire system has been traversed, as shown in Figure 16.9.

The same Chain of Responsibility implemented as a linear chain.

Figure 16.9. The same Chain of Responsibility implemented as a linear chain.

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 that we conveniently used in this example. It 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 Java

As just noted, while we were discussing help systems, any sequence of contained components such as JComponent/Panel/Frame causes a cascade of events to be sent upward to the surrounding components. This is a clear implementation of the Chain of Responsibility pattern. It could also be argued that, in general, the class inheritance structure itself exemplifies this pattern. If a method is 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 matter.

Consequences of the Chain of Responsibility

Use of the Chain of Responsibility pattern has the following consequences:

  1. This pattern's primary purpose, like that of several other patterns, is to reduce coupling between objects. An object needs to know only how to forward the request to other objects.

  2. Each Java object in the chain is self-contained. It knows nothing of the others and needs only to decide whether it can satisfy the request. This makes writing each one, as well as constructing the chain, very easy.

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

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

Thought Questions

  1. How could you use the Chain of Responsibility pattern to implement an e-mail filter?

  2. Write a Java servlet for processing a Common Gateway Interface (CGI) form using the Chain of Responsibility pattern.

Programs on the CD-ROM

Program Description

ChainOfResponsibilityImageChainChainer.java

Allows the entry of commands that cause visual objects in the screen interface to change.

ChainOfResponsibilityHelpChainHelpChain.java

Shows how a simple help system can pass messages to increasingly more general components.
..................Content has been hidden....................

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