Chain of Responsibility pattern
Chain of Responsibility
Motivation
In writing an application of any kind, it often happens that the event generated by one object needs to be handled by another one. And, to make our work even harder, we also happen to be denied access to the object which needs to handle the event. In this case there are two possibilities: there is the beginner/lazy approach of making everything public, creating reference to every object and continuing from there and then there is the expert approach of using the Chain of Responsibility.The Chain of Responsibility design pattern allows an object to send a command without knowing what object will receive and handle it. The request is sent from one object to another making them parts of a chain and each object in this chain can handle the command, pass it on or do both. The most usual example of a machine using the Chain of Responsibility is the vending machine coin slot: rather than having a slot for each type of coin, the machine has only one slot for all of them. The dropped coin is routed to the appropriate storage place that is determined by the receiver of the command.
- Intent:
- It avoids attaching the sender of a request to its receiver, giving this way other objects the possibility of handling the request too.
- The objects become parts of a chain and the request is sent from one object to another across the chain until one of the objects will handle it.
Implementation
The UML diagram of classes below will help us understand better the way the Chain works.- In the diagram above some explanations are needed on what is the role of every class:
- Handler - defines an interface for handling requests
- RequestHandler - handles the requests it is responsible for
- If it can handle the request it does so, otherwise it sends the request to its successor
The classic example of the Chain of Responsibility's implementation is presented for us below:
public class Request { private int m_value; private String m_description; public Request(String description, int value) { m_description = description; m_value = value; } public int getValue() { return m_value; } public String getDescription() { return m_description; } } public abstract class Handler { protected Handler m_successor; public void setSuccessor(Handler successor) { m_successor = successor; } public abstract void handleRequest(Request request); } public class ConcreteHandlerOne extends Handler { public void handleRequest(Request request) { if (request.getValue() < 0) { //if request is eligible handle it System.out.println("Negative values are handled by ConcreteHandlerOne:"); System.out.println("\tConcreteHandlerOne.HandleRequest : " + request.getDescription() + request.getValue()); } else { super.handleRequest(request); } } } public class ConcreteHandlerThree extends Handler { public void handleRequest(Request request) { if (request.getValue() >= 0) { //if request is eligible handle it System.out.println("Zero values are handled by ConcreteHandlerThree:"); System.out.println("\tConcreteHandlerThree.HandleRequest : " + request.getDescription() + request.getValue()); } else { super.handleRequest(request); } } } public class ConcreteHandlerTwo extends Handler { public void handleRequest(Request request) { if (request.getValue() > 0) { //if request is eligible handle it System.out.println("Positive values are handled by ConcreteHandlerTwo:"); System.out.println("\tConcreteHandlerTwo.HandleRequest : " + request.getDescription() + request.getValue()); } else { super.handleRequest(request); } } } public class Main { public static void main(String[] args) { // Setup Chain of Responsibility Handler h1 = new ConcreteHandlerOne(); Handler h2 = new ConcreteHandlerTwo(); Handler h3 = new ConcreteHandlerThree(); h1.setSuccessor(h2); h2.setSuccessor(h3); // Send requests to the chain h1.handleRequest(new Request("Negative Value ", -1)); h1.handleRequest(new Request("Negative Value ", 0)); h1.handleRequest(new Request("Negative Value ", 1)); h1.handleRequest(new Request("Negative Value ", 2)); h1.handleRequest(new Request("Negative Value ", -5)); } } |
Applicability & Examples
- Having so many design patterns to choose from when writing an application, it's hard to decide on which one to use, so here are a few situations when using the Chain of Responsibility is more effective:
- More than one object can handle a command
- The handler is not known in advance
- The handler should be determined automatically
- It’s wished that the request is addressed to a group of objects without explicitly specifying its receiver
- The group of objects that may handle the command must be specified in a dynamic way
Example 1
In designing the software for a system that approves the purchasing requests.In this case, the values of purchase are divided into categories, each having its own approval authority. The approval authority for a given value could change at any time and the system should be flexible enough to handle the situation.
The Client in the example above is the system in need of the answer to the approval. It sends a request about it to an purchase approval authority. Depending on the value of the purchase, this authority may approve the request or forward it to the next authority in the chain.
For example let’s say a request is placed for the purchase of a new keyboard for an office. The value of the purchase is not that big, so the request is sent from the head of the office to the head of the department and then to the materials department where it stops, being handled locally. But if equipment for the whole department is needed then the request goes form the head of the department, to materials department, to the purchase office and even to the manager if the value is too big.
Example 2
In designing the software that uses a set of GUI classes where it is needed to propagate GUI events from one object to another.When an event, such as the pressing of a key or the click of the mouse, the event is needed to be sent to the object that has generated it and also to the object or objects that will handle it.
The Client is, of course, the object that has generated the event, the request is the event and the handlers are the objects that can handle it. So, if we have a handler for the click of the mouse, a handler for the pressing of the ‘Enter’ key and a handler for the pressing of the ‘Delete’ key, that is the chain of handlers that take care of the events that are generated.
Example 3
In designing a shipping system for electronic orders.The steps to complete and handle the order differs form one order to another based on the customer, the size of the order, the way of shipment, destination and more other reasons. The business logic changes also as special cases appear, needing the system to be able to handle all cases.
The Client, the electronic order in process, requests shipping based on a set of pieces of information. Its request is turned by the system into a specific form, combining the steps to completing and the details of handling, based on the input information. The system will send this type of request through a chain of order-handlers until the input information that it comes with matches the input the order-handles takes. When special cases appear, all that is needed is a new handler to be added in the chain.
Specific problems and implementation
The classic implementation of the Chain of Responsibility is just the first step in applying the pattern to our own application. Improvements based on the type of commands we are handling are needed, in order to make the use of this pattern effective.Representing requests
In real life each handler represents a system. And each system can handle specific requests or requests common to more handlers. We should take this issue in consideration when we implement this pattern. In the classical samples of the CoR found on the net you can see that the request is generally represented by an integer. Of course in real life we can not use primary data types as a request.A clever design should be a flexible one. The best solution here is to create an interface a super class Request (or and interface) where to the default behavior. Then if we need to add a new handler and a specific request all we need is to extend the Request base class.
Of course this is not the only approach. Let’s consider the shipping system example. Each request will have to contain a large amount of data. Creating request examples for this might be difficult. We can take some xml objects containing the data, generated during the application flow (let’s assume we already have the code implemented for that) and pass them to each handler.
Or since the data was already saved in the database (let’s assume that also) we can pass only the id’s of the involved objects and then each handler will take the data required from db.
Unhandled requests
Unfortunately, the Chain doesn't guarantee that every command is handled, which makes the problem worse, since unhandled commands propagate through the full length of the chain, slowing down the application. One way to solve this is by checking if, at the end of the chain, the request has been handled at least once, otherwise we will have to implement handlers for all the possible requests that may appear.
Broken Chain
Sometimes we could forget to include in the implementation of the handleRequest method the call to the successor, causing a break in the chain. The request isn’t sent forward from the broken link and so it ends up unhandled. A variation of the pattern can be made to send the request to all the handlers by removing the condition from the handler and always calling the successor.The following implementation eliminates the Broken Chain problem. The implementation moves the code to traverse the chain into the base class keeping the request handling in a different method in the subclasses. The handleRequest method is declared as final in the base class and is responsible to traverse the chain. Each Handler have to implement the handleRequestImpl method, declared as abstract in the super class.
public abstract class Handler{ private Handler m_successor; public void setSuccessor(Handler successor) { m_successor = successor; } protected abstract boolean handleRequestImpl(Request request); public final void handleRequest(Request request) { boolean handledByThisNode = this.handleRequestImpl(request); if (m_successor != null && !handledByThisNode) { m_successor.handleRequest(request); } } } protected boolean handleRequestImpl(Request request) { if (request.getValue() < 0) { //if request is eligible handle it System.out.println("Negative values are handled by ConcreteHandlerOne:"); System.out.println("\tConcreteHandlerOne.HandleRequest : " + request.getDescription() + request.getValue()); return true; } else { return false; } } |
public final void handleRequest(Request request) { boolean handledByThisNode = this.handleRequestImpl(request); if (m_successor != null && !handledByThisNode) m_successor.handleRequest(request); } |
Avoiding spam requests
For example, an improvement that we could find useful is avoiding sending spam commands. This way, the concrete extension of the HandleRequest function will look like this:public void HandleRequest(int request) { if(isSpam(request)) { // if the request is spam take spam-related actions ... } else { // request is not spam. super.HandleRequest(request); // Pass message to next filter in the chain. } } |
Use on existing code
The last, but not least problem that the Chain of Responsibility creates to a programmer is the fact that it is impossible to introduce the pattern into the existing classes without modifying the source code and, even in the case where the pattern is already included in the code, if new operations need to be added to the Handler, it is impossible to do that without modifying the source code. So the basic idea is to decide from the start on whether to use the pattern or not and if we do, what methods we need.- The fundamental flaw of the pattern is the fact that it gets easily broken: if the programmer forgets to call the next handler in the concreteHandler the request gets lost on the way. This problem comes from the fact that the execution is not handled entirely by the superclass and the call is triggered in the superclass.
- When implementing the CoR pattern a special care should be taken for the request representation. The request is not considered a distinctive part of the CoR pattern, but it is still used in all the components of the pattern.
- Another flaw of the Chain of Responsibility is the fact that some requests may end up unhandled due to the wrong implementation of concrete handler, their propagation slowing down the rest of the application. This means that extra care is needed when taking into account the requests that may appear in the process.
Comments
Post a Comment
Please post comments here:-)