Chain of Responsibility Design Pattern: Clean Code Recipe

Mohammad Aamir
The Startup
Published in
4 min readAug 15, 2020

Intent

Chain of responsibility design pattern decouples the sender of a request from its potential receivers. The receivers form a chain, and the request travels along this chain. The first object receives the request. If it’s not interested in the specific request, it forwards it to the next receiver. The request travels until one of the objects processes the request or until we reach the end of the chain. The chain is implemented as a single-linked list with each receiver holding a reference to its successor in the chain. The end of the chain is either a new reference or a reference to a special type.

We can manage the chain of responders dynamically by adding new responders or removing existing ones from the list. In some cases, the responder may forward the request through the chain after it handles it. This way, multiple responders can process the same request. This is how iOS or MacOS handle house clicks and taps on the user interface. The chain of responsibility stands at the core of user event processing in Cocoa and Cocoa Touch.

Disadvantage

The chain of responsibility is a popular pattern that can address various problems that require passing the request between objects organised in a sequence. However, you shouldn’t use it when each request is only handled by one responder, or when the number of request handlers is limited.

Problem

Imagine that you’re working on a customer case system. You want to restrict access to the system so only authenticated users can resolve issue. Also, users who have administrative permissions must have full access to all disputes.

You realised that on administrative users the work load is too much and you want some load management that the minor issues should be resolved by junior care representatives, if it is not handled by them they will fall back to senior care representatives, still if the issue is not resolved by senior users then they will fall in to expert care representatives.

Solution

Like many other behavioral design patterns, the Chain of Responsibility relies on transforming particular behaviors into stand-alone objects called handlers. In our case, each check should be extracted to its own class with a single method that performs the check. The request, along with its data, is passed to this method as an argument.

The pattern suggests that you link these handlers into a chain. Each linked handler has a field for storing a reference to the next handler in the chain. In addition to processing a request, handlers pass the request further along the chain. The request travels along the chain until all handlers have had a chance to process it.

Here’s the best part: a handler can decide not to pass the request further down the chain and effectively stop any further processing.

Example

Let’s make a simple example to illustrate how we can use this pattern. We are going to us Swift for this.

First, we define an protocol (IssueHandler):

protocol IssueHandling {
init(next: IssueHandling?)
func handle(level: Severity)
}

Create enum to specify complexity level

enum Severity {
case easy
case medium
case hard
}

Now lets create concrete classes

final class ExpertHandler: IssueHandling {
var next: IssueHandling?
init(next: IssueHandling?) {
self.next = next
}
func handle(level: Severity) {
if case level = Severity.hard {
print("Request handled by expert")
} else {
print("Request can't be handled by expert")
next?.handle(level: level)
}
}
}
final class SeniorHandler: IssueHandling {
var next: IssueHandling?
init(next: IssueHandling?) {
self.next = next
}
func handle(level: Severity) {
if case level = Severity.medium {
print("Request handled by senior")
} else {
print("Request can't be handled by senior")
next?.handle(level: level)
}
}
}
final class JuniorHandler: IssueHandling {
var next: IssueHandling?
init(next: IssueHandling?) {
self.next = next
}
func handle(level: Severity) {
if case level = Severity.easy {
print("Request handled by junior")
} else {
print("Request can't be handled by junior")
next?.handle(level: level)
}
}
}

This is how we can use it

let expertHandler = ExpertHandler(next: nil)
let seniorHandler = SeniorHandler(next: expertHandler)
let juniorHandler = JuniorHandler(next: seniorHandler)
juniorHandler.handle(level: .hard)

The printed result will be:

Request can't be handled by junior
Request can't be handled by senior
Request handled by expert

--

--

Mohammad Aamir
The Startup

A developer, likes to code for iOS and react-native apps.