Skip to content

Chain of Responsibility Design Pattern

Video Lecture

Section Video Links
Chain of Responsibility Chain of Responsibility Overview Chain of Responsibility Overview 
Use Case Chain of Responsibility Use Case Chain of Responsibility Use Case 
Python Floor Division Python Floor Division Python Floor Division 
Accepting User Input Accepting User Input Accepting User Input 

Overview

Chain of Responsibility pattern is a behavioral pattern used to achieve loose coupling in software design.

In this pattern, an object is passed to a Successor, and depending on some kind of logic, will or won't be passed onto another successor and processed. There can be any number of different successors and successors can be re-processed recursively.

This process of passing objects through multiple successors is called a chain.

The object that is passed between each successor does not know about which successor will handle it. It is an independent object that may or may not be processed by a particular successor before being passed onto the next.

The chain that the object will pass through is normally dynamic at runtime, although you can hard code the order or start of the chain, so each successor will need to comply with a common interface that allows the object to be received and passed onto the next successor.

Terminology

  • Handler Interface: A common interface for handling and passing objects through each successor.
  • Concrete Handler: The class acting as the Successor handling the requests and passing onto the next.
  • Client: The application or class that initiates the call to the first concrete handler (successor) in the chain.

Chain of Responsibility UML Diagram

Chain of Responsibility Design Pattern

Source Code

In this concept code, a chain is created with a default first successor. A number is passed to a successor, that then does a random test, and depending on the result will modify the number and then pass it onto the next successor. The process is randomized and will end at some point when there are no more successors designated.

./chain_of_responsibility/chain_of_responsibility_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# pylint: disable=too-few-public-methods
"The Chain Of Responsibility Pattern Concept"
import random
from abc import ABCMeta, abstractmethod

class IHandler(metaclass=ABCMeta):
    "The Handler Interface that the Successors should implement"
    @staticmethod
    @abstractmethod
    def handle(payload):
        "A method to implement"

class Successor1(IHandler):
    "A Concrete Handler"
    @staticmethod
    def handle(payload):
        print(f"Successor1 payload = {payload}")
        test = random.randint(1, 2)
        if test == 1:
            payload = payload + 1
            payload = Successor1().handle(payload)
        if test == 2:
            payload = payload - 1
            payload = Successor2().handle(payload)
        return payload

class Successor2(IHandler):
    "A Concrete Handler"
    @staticmethod
    def handle(payload):
        print(f"Successor2 payload = {payload}")
        test = random.randint(1, 3)
        if test == 1:
            payload = payload * 2
            payload = Successor1().handle(payload)
        if test == 2:
            payload = payload / 2
            payload = Successor2().handle(payload)
        return payload

class Chain():
    "A chain with a default first successor"
    @staticmethod
    def start(payload):
        "Setting the first successor that will modify the payload"
        return Successor1().handle(payload)

# The Client
CHAIN = Chain()
PAYLOAD = 1
OUT = CHAIN.start(PAYLOAD)
print(f"Finished result = {OUT}")

Output

python ./chain_of_responsibility/chain_of_responsibility_concept.py
Successor1 payload = 1
Successor2 payload = -1
Successor2 payload = -0.5
Successor2 payload = -0.25
Successor1 payload = -0.5
Successor1 payload = 0.5
Successor2 payload = -1.5
Finished result = -1.5

SBCODE Editor

<>

Example Use Case

In the ATM example below, the chain is hard coded in the client first to dispense amounts of £50s, then £20s and then £10s in order.

This default chain order helps to ensure that the minimum number of notes will be dispensed. Otherwise, it might dispense 5 x £10 when it would have been better to dispense 1 x £50.

Example UML Diagram

Chain of Responsibility Design Pattern

Source Code

./chain_of_responsibility/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"An ATM Dispenser that dispenses denominations of notes"
import sys
from atm_dispenser_chain import ATMDispenserChain

ATM = ATMDispenserChain()
AMOUNT = int(input("Enter amount to withdrawal : "))
if AMOUNT < 10 or AMOUNT % 10 != 0:
    print("Amount should be positive and in multiple of 10s.")
    sys.exit()
# process the request
ATM.chain1.handle(AMOUNT)
print("Now go spoil yourself")

./chain_of_responsibility/atm_dispenser_chain.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"The ATM Dispenser Chain"
from dispenser10 import Dispenser10
from dispenser20 import Dispenser20
from dispenser50 import Dispenser50

class ATMDispenserChain:  # pylint: disable=too-few-public-methods
    "The Chain Client"

    def __init__(self):
        # initializing the successors chain
        self.chain1 = Dispenser50()
        self.chain2 = Dispenser20()
        self.chain3 = Dispenser10()
        # Setting a default successor chain that will process the 50s
        # first, the 20s second and the 10s last. The successor chain
        # will be recalculated dynamically at runtime.
        self.chain1.next_successor(self.chain2)
        self.chain2.next_successor(self.chain3)

./chain_of_responsibility/interface_dispenser.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
"The ATM Notes Dispenser Interface"
from abc import ABCMeta, abstractmethod

class IDispenser(metaclass=ABCMeta):
    "Methods to implement"
    @staticmethod
    @abstractmethod
    def next_successor(successor):
        """Set the next handler in the chain"""

    @staticmethod
    @abstractmethod
    def handle(amount):
        """Handle the event"""

./chain_of_responsibility/dispenser10.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"A dispenser of £10 notes"
from interface_dispenser import IDispenser

class Dispenser10(IDispenser):
    "Dispenses £10s if applicable, otherwise continues to next successor"

    def __init__(self):
        self._successor = None

    def next_successor(self, successor):
        "Set the next successor"
        self._successor = successor

    def handle(self, amount):
        "Handle the dispensing of notes"
        if amount >= 10:
            num = amount // 10
            remainder = amount % 10
            print(f"Dispensing {num} £10 note")
            if remainder != 0:
                self._successor.handle(remainder)
        else:
            self._successor.handle(amount)

./chain_of_responsibility/dispenser20.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"A dispenser of £20 notes"
from interface_dispenser import IDispenser

class Dispenser20(IDispenser):
    "Dispenses £20s if applicable, otherwise continues to next successor"

    def __init__(self):
        self._successor = None

    def next_successor(self, successor):
        "Set the next successor"
        self._successor = successor

    def handle(self, amount):
        "Handle the dispensing of notes"
        if amount >= 20:
            num = amount // 20
            remainder = amount % 20
            print(f"Dispensing {num} £20 note(s)")
            if remainder != 0:
                self._successor.handle(remainder)
        else:
            self._successor.handle(amount)

./chain_of_responsibility/dispenser50.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"A dispenser of £50 notes"
from interface_dispenser import IDispenser

class Dispenser50(IDispenser):
    "Dispenses £50s if applicable, otherwise continues to next successor"

    def __init__(self):
        self._successor = None

    def next_successor(self, successor):
        "Set the next successor"
        self._successor = successor

    def handle(self, amount):
        "Handle the dispensing of notes"
        if amount >= 50:
            num = amount // 50
            remainder = amount % 50
            print(f"Dispensing {num} £50 note(s)")
            if remainder != 0:
                self._successor.handle(remainder)
        else:
            self._successor.handle(amount)

Output

python ./chain_of_responsibility/client.py
Enter amount to withdrawal : 180
Dispensing 3 £50 note(s)
Dispensing 1 £20 note(s)
Dispensing 1 £10 note
Now go spoil yourself

SBCODE Editor

<>

New Coding Concepts

Floor Division

Normally division uses a single / character and will return a float even if the numbers are integers or exactly divisible with no remainder,

E.g.,

PS> python
>>> 9 / 3
3.0

Python Version 3 also has an option to return an integer version (floor) of the number by using the double // characters instead.

PS> python
>>> 9 // 3
3

See PEP-0238 : https://www.python.org/dev/peps/pep-0238/

Accepting User Input

In the file ./chain_of_responsibility/client.py above, there is a command input.

The input command allows your script to accept user input from the command prompt.

In the ATM example, when you start it, it will ask the user to enter a number.

Then, when the user presses the enter key, the input is converted to an integer and the value tested if valid.

6
7
8
AMOUNT = int(input("Enter amount to withdrawal : "))
if AMOUNT < 10 or AMOUNT % 10 != 0:
    ...continue

Note that in Python 2.x, use the raw_input() command instead of input().

See PEP-3111 : https://www.python.org/dev/peps/pep-3111/

Summary

In the Chain of Responsibility,

  • The object/payload will propagate through the chain until fully processed.
  • The object does not know which successor or how many will process it.
  • The next successor in the chain can either be chosen dynamically at runtime depending on logic from within the current successor, or hard coded if it is more beneficial.
  • Successors implement a common interface that makes them work independently of each other, so that they can be used recursively or possibly in a different order.
  • A user wizard, or dynamic questionnaire are other common use cases for the chain of responsibility pattern.
  • Consider the Chain of Responsibility pattern like the Composite pattern (structural) but with logic applied (behavioral).