0 XP · 0 scenes

Chain of Responsibility

Pass a request along handlers until one deals with it
The problem

The expense code is one long if/elif: if amount <= 1000 lead approves, elif amount <= 10000 manager, elif ... The rules for who approves are hard-coded in one place, and changing the order or adding a level means editing it.

The idea

Give every handler a link to the next one.

Reach for it when

More than one object may handle a request and you do not know which

1

The picture from life

A customer-care call. First the front-line agent tries; if the issue is too big it goes to the senior agent, and if still too big, to the manager. The caller does not choose who answers. The request simply travels up the chain until someone can handle it.

2

The problem

What goes wrong when you do not use it.

The expense code is one long if/elif: if amount <= 1000 lead approves, elif amount <= 10000 manager, elif ... The rules for who approves are hard-coded in one place, and changing the order or adding a level means editing it.

✗ Before
Clientapprove(amount)if amount <= 1000: leadelif amount <= 10000: managerelif amount <= 100000: directorelse: rejected
  • calls / uses
One function holds the whole approval ladder. Changing the order or adding a level means editing it.
Reach for it when
  • More than one object may handle a request and you do not know which
  • The set or order of handlers changes
  • You want to add or remove steps like middleware
3

The idea

How the pattern fixes it.

Give every handler a link to the next one. A handler either deals with the request or passes it on. The sender only knows the first handler.

✓ After
Clientleadlimit 1000managerlimit 10000directorlimit 100000approve()nextnext
  • calls / uses
Each approver handles what it can and passes the rest on. The client only knows the first one.

Who is who

Approver (handler)
Has a limit and a link to the next approver.
approve(amount)
Handle it if within limit, otherwise forward.
Client
Builds the chain and calls the first handler.

Build it in steps

  1. 1Define a handler with a reference to the next.
  2. 2In handle(): decide, else call next.handle().
  3. 3Link the handlers in the order you want.
  4. 4Send requests only to the first one.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

Clientleadmanager1approve(5000)2approve(5000)3'manager approves 5000'4'manager approves 5000'
Step 1/4

The client sends the request to the first approver only.

approve(5000)
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class Approver:
    def __init__(self, name, limit, next_approver=None):
        self.name = name
        self.limit = limit
        self.next_approver = next_approver

    def approve(self, amount):
        if amount <= self.limit:
            return f"{self.name} approves {amount}"
        if self.next_approver is None:
            return f"nobody can approve {amount}"
        return self.next_approver.approve(amount)        # pass it on


def demo():
    chain = Approver("lead", 1000, Approver("manager", 10000, Approver("director", 100000)))
    return [chain.approve(500), chain.approve(5000), chain.approve(50000), chain.approve(500000)]

What you gain

  • Sender and handler are decoupled
  • Reorder or extend the chain freely

What it costs

  • A request may fall off the end unhandled
  • Hard to see at a glance who handled it

You have already met it in

  • Servlet filters and web middleware (auth, logging, compression)
  • Java's try / catch blocks passing exceptions up
  • DOM event bubbling

Often mixed up with

  • DecoratorDecorator always passes the call inward and adds to it; a chain handler may stop the request.
  • CommandCommand is a request turned into an object; the chain decides who handles a request.
6

Make it stick

Build a support-ticket chain: Bot (FAQ), Agent, Manager. Each handles only some severities and passes the rest.

Confidence