Chain of Responsibility Design Pattern

Classic GOF Behaviorial Design Patterns

 
  

Intent

The Chain of Responsibility Pattern allows you to pass requests along a chain of handlers. As each handler receives the request, it decides whether to (or whether it can) process the request or to pass it to next handler in the chain - or do both.

It is used to:

  • Avoid coupling of a request sender from a request receiver by giving one or more objects a chance to handle the request.
  • Create a single processing pipeline that can hadle many different types of requests, typically one per handler.
  • Pick a processing strategy dynamically at processing time.
chain patterns
  

Problem

The Chain of Responsibility Pattern is used to provide a solution to a common problem - where you have many possbile ways of handling a command or request that needs to be determined at runtime or where you have a single request pipeline that can have many possbile outputs. As an additional benefit, the requester does not have to know anything about the which handler is actually doing the processing.

  

Discussion

The Chain of Responsibility Pattern, at its most basic level, can be thought of as a "pipeline." Requesters or clients simply deposit their requests at the start of the pipeline in a "fire and forget" approach. The requests then flow through the pipeline from handler to handler until one of them can can actually process the request.

The key fact about the Chain of Responsibility pattern is that the number of handlers is completely unknown by the caller - the only thing it is aware of is the entrance to that pipeline. Because of this, handlers can be added or removed as needed or as requirements change without affecting the basic operation of the system using it - they could even, depending on the platform, be provisioned dynamically.


chain of responsibility pattern simplified

As pointed out, Chain of Responsibility pattern allows the use of very simple object interconnections. Clients or requesters need only know about the "head" of the pipeline. The handlers need only keep a reference to the handler immediately after them in the chain

The most common problem found when looking implementing a Chain of Responsibility pattern is in forgetting to include a final "fall through" handler to ensure that any unhandled requests are caught. Think in terms of a switch or case statement in many programming languages - they should all have a final default in case any of the specific testsdo not succeed.

The Chain of Responsibility pattern shouldn't be used, because of its increased complexity, when:

  • the client has to know which handler is processing the request or command
  • there is only one handler
  • the client has to be aware of or linked to every possbile handler
  

Structure

The structure of the Chain of Responsibility is fairly straightforward in almost every programming language. Fundamentally, it consists of a series of concrete handler objects which extend or implement a base handler definition (IHandler or BaseHandler).

The client sends a request to be handled to the chain of handlers - all of which extend IHandler. Typically, each handler will take a turn at attempting to handle the request. If the initial handler is successful, the sequence terminates and the request goes no further down the chain. If the initial handler is unable to process the request, it then passes the request on to the next handler. This process continues until either the request is handled or it ends up in the final default hadler.

There are, of course, a number of variations in how this pipeline or "chain" can be implemented. For example, it is possible that instead of a request being processed by only a single handler, we might want a request to be processed by every handler that is appropriate. In that case, instead of terminating the processing with the first success, the request is passed down the chain regardless of any success that may have occurred. Typially, in this scenario, additional metadata is passed along with the request to indicate any prior successes. It is also important to note that that in this scenario with more than one potential processor (handler), we normally see that the request is copied for use by each handler and the original message is passed down the chain unmodified. This can, naturally, vary depending on your specific needs. Of course, this approach starts to become very similar to a typical enterprise pattern like Publish and Subscribe.

Another common variation is that each handler will contain an additional method that can be used as a "pre-processor" that allows the handler to pre-determine if it is able to handle a request before expending resources on the actual work. This is often as basic as examining some basic metadata included with the request such as filetype or another parameter.


chain of responsibility pattern structure

In this basic diagram of the Chain of Responsibility Pattern the Client only knows of and communicates with a pipeline object (or, more specifically, the entry or "head-end" point to the pipeline)

The structure is straightforward and consists of:

  • IHandler - an interface or abstract class that defines the basic structure of the concrete handlers. Typically, this includes, at a minimum, the handle function along with a reference to the next handler in the chain, and, optionally, another method that can be used to perform test if this is the appropriate handler.
  • Client - the originator of the request
  • Concrete Handlers - implements the IHandler interface and holds the referene to the next handler in the chain
  

Practical Example

For a practical example of the Chain of Responsibility Pattern in a real world usage scenario, lets focus on a simple scenario - a document conversion pipeline. Imagine that you are tasked with building a service that can accept documents of varying types and convert them into valid html representations. You might be required to do this because you need to display these documents to end-users via a browser even if the user does not have every application that might have generated the otiginal document type. For example, how many of you have lotus 1-2-3 installed on your local machine? If your company has been around for a while, it is entirely possible that you may have a number of these file types floating around on your storage media.

Given this scenario, it is entirely possible to build a processor or handler that can convert this one particular document type. But what if you also have some other document types you need to display? Excel, WordPerfrect, DocMinder? Writing a separate program for each possible document type without a pattern such as the Chain of Responsibility would require that the client would have to contain a great deal of logic to route the requests to the appropriate end-point for conversion. It will also necessitate that the client be updated and be redeployed to users whenever a new document type was added. Of course, some of this could be abstracted via a service or routing boundary - but, that would require that it have similar knowledge and complexity. This scenario is where the Chain of Responsibility pattern comes to the rescue.

For our practical example, we could create a series of handlers where each handler can process a single document type. Chained together, these will form a "processong" pipeline that can be extended easily as future document types are added.


gof chain of responsibility example

Much like the generic diagram in STRUCTURE above, the chain of responsibility will an efficient way of processing varying types of documents that can be added to over time.

Let's expand on this practical example with some code below.

  

Code Samples

Below, you will find examples of the Chain of Responsibility Pattern implemented in four different commonly used languages. Each one of the examples implements the exact same scenario as described in the PRACTICAL EXAMPLE section above.

Each sample will use a chain of responsibility to allow for easily manage a document conversion pipeline efficiently.


C# Chain of Responsibility Code Sample

In this C# example, the Chain of Responsibility pattern is used.


Java Chain of Responsibility Code Sample

In this Java example, the Chain of Responsibility pattern is used.


Typescript Chain of Responsibility Code Sample

In this TypeScript example, the Chain of Responsibility pattern is used.


JavaScript Chain of Responsibility Code Sample

In this JavaScript example, the Chain of Responsibility pattern is used.


  

Considerations

  1. Ensure that you need the Chain of Responsibility pattern - typically when you need to process multiple "requests" from the same pipeline,
  2. Ensure you understand the rules for understanding when a request can be specifically processed by a hnadler.
  3. If you use a base class, ensure that it contains the pointer to the next object in the chain.
  4. If you use an interface to define the handlers, be sure that it contains function placeholders for the handle(), nextHandler(), and the oprtional canHandle() methods.
  5. Each of the concrete handler implementations should be able to handle a single request.
  6. Each handler should also contain the logic to "pass" the request to the next one in the chain if it is unable to process it - typically via the base class "nextHandler" method.
  7. Be sure to include a default or "fall through" handler in case none of the handlers canprocessthe request.
  8. Some object that composes the "chain" or pipeline needs to be created and maintained - it should tell each of the handlers their place in the chain.
  9. A mechanism to deposit or leave requests at the start of the chain must be created.
  

Additional Notes

  • The Chain of Responsibility pattern is often used in conjunction with the Gand of Four Composite Pattern - allowing a component's parent can act as its successor.
  • Increases overall code complexity by creating multiple additional classes.
  • Only use the Chain of Responsibility pattern when your program is expected to process various types of requests in various ways - but the exact types and the sequence of operations are unknown in advance.
  • Use thepattern when it is essential to process a request through several handelrs but in a specific order.
  • This pattern is expecially handy when the order of handler processing needs to change at run time.
  • Similar, although not identical, functionality can be achieved with the Command, Mediator, and Observer patterns - although they all have varying trade-offs.
  • This pattern allows you to apply the Open/Closed Principle such that new handlers can be added or injected into the application without breaking any existing code.