Skip to content

Chain of Responsibility

kgleong edited this page Oct 23, 2015 · 1 revision

Motivation

The Chain of Responsibility design pattern decouples a request from the objects that can potentially handle it.

Because the client, which initiates the request, and the object that receives the request are not directly coupled, this creates the following opportunities:

  • Handler objects can be added and removed dynamically.
  • Multiple objects can act on a single request in a specified order.

Code

public abstract class Handler {
    Handler mSuccessorHandler;

    public abstract void handleRequest();

    public void setSuccessorHandler(Handler successorHandler) {
        mSuccessorHandler = successorHandler;
    }
}

A Handler object is the main component in the Chain of Responsibility pattern.

Handler instances contain a reference to a succeeding Handler object, allowing a client's request to either be handled by the Handler or passed along to its successor, or both.

The mSuccessorHandler references form a chain of Handler objects. Clients can then call handleRequest() on any Handler instance in the chain, and the request will move through the chain, giving any Handler it passes through an opportunity to perform some action on the request.

The base Handler class can be an abstract class, concrete class, or interface, depending on the situation.

public class ConcreteHandlerA extends Handler {
    public void handleRequest() {
        System.out.println("Passing request to ConcreteHandlerA's successor.");
        mSuccessorHandler.handleRequest();
    }
}

public class ConcreteHandlerB extends Handler {
    public void handleRequest() {
        System.out.println("ConcreteHandlerB handling request.");
    }
}

ConcreteHandlerA passes all requests through to its successor, whereas ConcreteHandlerB is an example of a Handler at the end of the chain.

In this case, ConcreteHandlerB actually handles the request, and ConcreteHandlerA does not.

This is a simple example, but many different variants are possible by modifying the handleRequest() method in the following way:

// Pass in a request object that contains any relevant information.
public void handleRequest(Request request) {

    // Handle the request if it meets certain criteria, and pass
    // it on if not.
    switch(request.type) {
        case Request.TYPE_A:
            System.out.println("Handling a request of type A");
            break;
        default:
            if(mSuccessorHandler != null) {
                mSuccessorHandler.handleRequest(request);
            }
    }
}

A Request object that encapsulates any request metadata or information needed by any interested Handler objects can be passed in as a parameter to the handleRequest() method.

This in turn allows each Handler in the chain to examine the Request passed in and choose to:

  • Handle the request and terminate the chain.
  • Pass it along to the next handler in the chain.
  • Some combination of these choices.
// Client usage

// Initialize the handler successor chain
ConcreteHandlerA handlerA = new ConcreteHandlerA();
ConcreteHandlerB handlerB = new ConcreteHandlerB();
handlerA.setSuccessorHandler(handlerB);

handlerA.handleRequest();

// Output
// Passing request to ConcreteHandlerA's successor.
// ConcreteHandlerB handling request.

From the client's viewpoint, once the handler succession chain is setup, the handleRequest() method can be called on any object in the chain.

The request will then move down the chain, as dictated by each Handler, which controls whether a request is handled or passed along.

Sometimes, a natural chain of Handler objects already exists, therefore removing the need to initialize an ordering. If an existing hierarchy cannot be adapted or used, however, initialization of a chain of responsibility is unavoidable.

Use Case

Consider a typical application that contains buttons, dialog boxes, and windows as part of its UI.

If a user uses a right click to select an element, a help dialog should be displayed for that specific element.

When the user right clicks on a button contained in a window:

  • If the button contains any relevant user help guides, it should be displayed in the help dialog.
  • If not, the help request will be passed to the next enclosing UI element. If a window is the immediate container for the button, then the window's help guide will be displayed if present.
  • The application help guide will be displayed as a last resort if no other UI elements have handled the help request.
public class View {
    View mParent;

    public View(View parent) {
        mParent = parent;
    }

    @Override
    public handleHelpRequest() {
        if(mParent != null) {
            mParent.handleHelpRequest();
        }
        else {
            ApplicationWindow.getInstance().handleHelpRequest();
        }
    }
}

public class Button extends View {
    public Button(View parent) {
        super(parent);
    }

    @Override
    public handleHelpRequest() {
        System.out.println("Passing request to button parent.");
        super.handleHelpRequest();
    }
}

public class Window extends View {
    public Window(View parent) {
        super(parent);
    }

    @Override
    public handleHelpRequest() {
        // Help exists for this window.
        // Do not pass the help request to the window's parent.
        System.out.println("Display help dialog for window.");
    }
}

Button and Window are subclasses of View, which contains a handleHelpRequest() method. The mParent field on any View class is an existing hierarchy reference that can also be used to define the handler chain for any view.

The handleHelpRequest() method checks to see if the current view has a parent, and if so it passes any help request on.

It makes sense to use a parent as the successor in the handler chain since parents normally sit behind their children when displayed on screen.

If there is no parent, then the singleton ApplicationWindow instance handles the help request as a final fallback.

public class ApplicationWindow extends Window {
    ApplicationWindow sInstance;

    public static ApplicationWindow getInstance() {
        if(sInstance == null) {
            sInstance = new ApplicationWindow();
        }

        return sInstance;
    }

    @Override
    public handleHelpRequest() {
        System.out.println("Display help dialog for application window.");
    }
}

The ApplicationWindow singleton instance is a special window that all UI elements are contained within. It serves as the root view for all other views.

Therefore, it's natural for the ApplicationWindow instance to be the final responder to any help requests, since requests work their way from foreground to background.

Below are two examples, one with a button enclosed by a window, and the other with only a standalone button.

// Button within a window
Window window = new WIndow();
Button button = new Button(window);

// User right clicks on the button
button.handleHelpRequest();

// Output:
// Passing request to button parent.
// Display help dialog for window.

The request terminates after the parent window handles it.

// Button with no parent window
Button button = new Button();

// User right clicks on the button
button.handleHelpRequest();

// Output:
// Passing request to button parent.
// Display help dialog for application window.

The request falls all the way to the ApplicationWindow instance, which handles the request and terminates the chain.

References

Clone this wiki locally