Chain of Responsibility Design Pattern
Video Lecture
Overview
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Terminology
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Chain of Responsibility UML Diagram

Source Code
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
./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
|
Example Use Case
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Example UML Diagram

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
|
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.,
Python Version 3 also has an option to return an integer version (floor) of the number by using the double // characters instead.
See PEP-0238 : https://www.python.org/dev/peps/pep-0238/
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.
| 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
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.