8. Behavioral Patterns
Chain of Responsibility
Design PatternCommand
Design PatternIterator
Design PatternMediator
Design PatternObserver
Design PatternState
Design PatternStrategy
Design Pattern- Template Method Design Pattern
- Visitor Design Pattern
8.1 Chain of Responsibility Design Pattern
Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers
.
Upon receiving a request, each handler decides either to process the request or to pass
it to the next handler in the chain.
# Realtime Usage
* ATM, Vending Machine
* Design Logger
Example - 1
https://sbcode.net/python/chain_of_responsibility/
import sys
class IDispenser:
"""Methods to implement"""
def next_successor(self, successor):
"""Set the next handler in the chain"""
pass
def handle(self, amount):
"""Handle the events(dispensing of notes)"""
pass
class Dispenser(IDispenser):
""" Added this to remove common function """
def __init__(self):
self._successor = None
def dispense_amount(self, amount, note):
if amount >= note:
num = amount // note
remainder = amount % note
print(f"Dispensing {num} Rs {note} note - from {type(self).__name__}")
# OR self.__class__.__name__
if remainder != 0:
self._successor.handle(remainder)
else:
self._successor.handle(amount)
class Dispenser10(Dispenser):
"""Dispenses Rs 10s if applicable, otherwise continues to next successor"""
def next_successor(self, successor):
self._successor = successor
def handle(self, amount):
self.dispense_amount(amount, 10)
class Dispenser20(Dispenser):
"""Dispenses Rs 20s if applicable, otherwise continues to next successor"""
def next_successor(self, successor):
self._successor = successor
def handle(self, amount):
self.dispense_amount(amount, 20)
class Dispenser50(Dispenser):
"""Dispenses Rs 50s if applicable, otherwise continues to next successor"""
def next_successor(self, successor):
self._successor = successor
def handle(self, amount):
self.dispense_amount(amount, 50)
class ATMDispenserChain:
"""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)
if __name__ =='__main__':
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()
ATM = ATMDispenserChain()
ATM.chain1.handle(AMOUNT) # process the request
print("Now go spoil yourself")
Example - 2
https://www.udemy.com/course/design-patterns-python/learn/lecture/13661562
class Creature:
def __init__(self, name, attack, defense):
self.defense = defense
self.attack = attack
self.name = name
def __str__(self):
return f'{self.name} ({self.attack}/{self.defense})'
class CreatureModifier:
def __init__(self, creature):
self.creature = creature
self.next_modifier = None
def add_modifier(self, modifier):
if self.next_modifier:
self.next_modifier.add_modifier(modifier)
else:
self.next_modifier = modifier
def handle(self):
if self.next_modifier:
self.next_modifier.handle()
class NoBonusesModifier(CreatureModifier):
def handle(self):
print('No bonuses for you!')
class DoubleAttackModifier(CreatureModifier):
def handle(self):
print(f'Doubling {self.creature.name}''s attack')
self.creature.attack *= 2
super().handle()
class IncreaseDefenseModifier(CreatureModifier):
def handle(self):
if self.creature.attack <= 2:
print(f'Increasing {self.creature.name}''s defense')
self.creature.defense += 1
super().handle()
if __name__ == '__main__':
goblin = Creature('Goblin', 1, 1)
print(goblin)
root = CreatureModifier(goblin)
root.add_modifier(NoBonusesModifier(goblin))
root.add_modifier(DoubleAttackModifier(goblin))
root.add_modifier(DoubleAttackModifier(goblin))
# no effect
root.add_modifier(IncreaseDefenseModifier(goblin))
root.handle() # apply modifiers
print(goblin)
8.2 Command Design Pattern
A Command Pattern says that encapsulate a request
under an object as a command and pass it to invoker object.
Invoker object
looks for the appropriate object which can handle this command and pass the command to the corresponding object and that object executes the command.
Consider we have a universal remote with four buttons On, Off, Up, and Down
.
This remote can be configured for any device such as a light, speaker, air conditioner
, and the buttons can be configured for the respective device’s use-cases.
- Example
- The Up/Down button can be configured to increase/decrease the
brightness
if the device is a light. - The Up/Down button can be configured to increase/decrease the
volume
if the device is a speaker.
- The Up/Down button can be configured to increase/decrease the
- https://refactoring.guru/design-patterns/command
class Device:
def on(self):
pass
# def off(self):
# pass
class Light(Device):
def on(self):
print("Turn on light")
class Speaker(Device):
def on(self):
print("Turn on speaker")
class Command:
def execute(self):
pass
class OnCommand(Command):
def __init__(self, device):
self.device = device
def execute(self):
self.device.on()
if __name__ == "__main__":
device1 = Light()
device2 = Speaker()
on1 = OnCommand(device1)
on1.execute()
on2 = OnCommand(device2)
on2.execute()
8.3 Iterator Design Pattern
Iterator is a behavioral design pattern that lets you traverse elements of a collection
without exposing its underlying representation (list, stack, tree, etc.).
- The Iterator will commonly contain two methods that perform the following concepts.
next
: returns the next object in the aggregate (collection, object).has_next
: returns a Boolean indicating if the Iterable is at the end of the iteration or not.
- The iterator protocol requires
__iter__()
to exposes the iterator__next__()
to return each of the iterated elements or stop-iteration
Applicability
https://refactoring.guru/design-patterns/iterator
Use the Iterator pattern when your collection has a complex data structure
under the hood,
but you want to hide its complexity from clients (either for convenience or security reasons).
Use the Iterator when you want your code to be able to traverse different data structures
or when types of these structures are unknown
beforehand.
Example - 1
https://sbcode.net/python/iterator/
class Iterable:
def __init__(self, collection):
self.index = 0
self.collection = collection
def has_next(self):
return self.index < len(self.collection)
def next(self):
if self.has_next():
collection = self.collection[self.index]
self.index += 1
return collection
raise Exception("AtEndOfIteratorException", "At End of Iterator")
if __name__ == '__main__':
AGGREGATES = [1, 22, 33, 'abc']
# AGGREGATES is a python list that is already iterable by default.
# but we can create own iterator on top anyway.
ITERABLE = Iterable(AGGREGATES)
while ITERABLE.has_next():
print(ITERABLE.next())
ITERABLE.next()
Example - 2
NAMES = ['SEAN','COSMO','EMMY']
ITERATOR = iter(NAMES)
print(ITERATOR.__next__())
print(ITERATOR.__next__())
print(ITERATOR.__next__())
NAMES = ['SEAN','COSMO','EMMY']
ITERATOR = NAMES.__iter__()
print(ITERATOR.__next__())
print(ITERATOR.__next__())
print(ITERATOR.__next__())
Example - 3
https://www.scaler.com/topics/design-patterns/iterator-design-pattern/
class Product:
def __init__(self, product_name, price):
self.product_name = product_name
self.price = price
def __str__(self):
return "{}: $ {}".format(self.product_name, self.price)
class CartIterator:
def __init__(self, products):
self.position = 0
self.products = products
def has_next(self):
return False if self.position >= len(self.products) else True
def next(self):
product = self.products[self.position]
self.position += 1
return product
def remove(self):
return self.products.pop()
class Cart:
def __init__(self):
self.products = []
def add(self, product):
self.products.append(product)
def iterator(self):
return CartIterator(self.products)
if __name__ == '__main__':
cart = Cart()
cart.add(Product("tooth paste", 15))
cart.add(Product("pen", 30))
cart.add(Product("bottle", 20))
print("Displaying Cart:")
iterator = cart.iterator()
while iterator.has_next():
product = iterator.next()
print(product)
print("Removing last product returned")
iterator.remove()
print("Displaying Cart:")
iterator = cart.iterator()
while iterator.has_next():
product = iterator.next()
print(product)
Example - 4
https://www.udemy.com/course/design-patterns-python/learn/lecture/13717864
# This has an issue if want to add another ability
class CreatureOld:
def __init__(self):
self.strength = 10
self.agility = 10
self.intelligence = 10
@property
def sum_of_stats(self):
return self.strength + self.agility + self.intelligence
@property
def max_stats(self):
return max(self.strength, self.agility, self.intelligence)
@property
def average_stats(self):
return self.sum_of_stats / 3.0
# Using Array backed or List backed property
class Creature:
_strength = 0
_agility = 1
_intelligence = 2
def __int__(self):
self.stats = [10, 10, 10]
@property
def strength(self):
return self.stats[Creature._strength]
@strength.setter
def strength(self, value):
self.stats[Creature._strength] = value
@property
def agility(self):
return self.stats[Creature._agility]
@agility.setter
def agility(self, value):
self.stats[Creature._agility] = value
@property
def intelligence(self):
return self.stats[Creature._intelligence]
@intelligence.setter
def intelligence(self, value):
self.stats[Creature._intelligence] = value
@property
def sum_of_stats(self):
return sum(self.stats)
@property
def max_stats(self):
return max(self.stats)
@property
def average_stats(self):
return float(sum(self.stats)/len(self.stats))
8.4 Mediator Design Pattern
Objects communicate through the Mediator
rather than directly with each other.
Mediator is a behavioral design pattern that lets you reduce chaotic dependencies
between objects.
The pattern restricts direct communications
between the objects and forces them to collaborate only via a mediator object.
Example - 1
https://www.udemy.com/course/design-patterns-python/learn/lecture/13661722
class Person:
def __init__(self, name):
self.name = name
self.rooms = None # []
def say(self, message):
self.rooms.broadcast(self.name, message)
class ChatRoom:
def __init__(self, name):
self.name = name
self.people = []
def broadcast(self, source, message):
if self.people:
print('Room: ', self.name)
for p in self.people:
if p.name != source:
s = f'{source}: {message}'
print(f'[{p.name}\'s inbox] {s}')
def join(self, person):
join_msg = f'{person.name} joins the chat'
self.broadcast('room', join_msg)
person.rooms = self # person.rooms.append(self)
self.people.append(person)
if __name__ == '__main__':
room1 = ChatRoom('R1')
john = Person('John')
alex = Person('Alex')
simon = Person('Simon')
room1.join(john)
room1.join(alex)
john.say('hi room')
alex.say('oh, hey john')
room1.join(simon)
simon.say('hi everyone!')
8.5 Observer Design Pattern
Observer is a behavioral design pattern that lets you define a subscription mechanism
to notify multiple objects about
any events that happen to the object they’re observing.
This design pattern is also referred to as Dependents
.
class User:
def __init__(self, userid):
self.userid = userid
class Group:
observers = []
def subscribe(self, user):
self.observers.append(user)
def unsubscribe(self, user):
self.observers.remove(user)
def notify(self, message):
for user in self.observers:
print(f'User {user.userid}', end=', ')
print(f'- received {message}')
if __name__ == '__main__':
g1 = Group()
u1 = User(1)
u2 = User(2)
u3 = User(3)
g1.subscribe(u1)
g1.subscribe(u2)
g1.subscribe(u3)
g1.notify('Message 1')
g1.unsubscribe(u1)
g1.notify('Message 2')
8.6 State Design Pattern
State is a behavioral design pattern that lets an object alter its behavior
when its internal state changes.
It makes the object flexible to alter its state
without handling a lot of if / else conditions.
State pattern ensures loose coupling between the performance of existing states versus the addition of new states.
Example - 1
https://www.udemy.com/course/design-patterns-python/learn/lecture/13682526
# interesting but not practical
class Switch:
def __init__(self):
self.state = OffState() # state changes to ON and OFF
def on(self):
self.state.on(self)
def off(self):
self.state.off(self)
class State:
def on(self, switch):
print('Light is already on')
def off(self, switch):
print('Light is already off')
class OnState(State):
def __init__(self):
print('Light turned on')
def off(self, switch):
print('Turning light off...')
switch.state = OffState()
class OffState(State):
def __init__(self):
print('Light turned off')
def on(self, switch):
print('Turning light on...')
switch.state = OnState()
if __name__ == '__main__':
sw = Switch()
sw.on() # Turning light on...
sw.off() # Turning light off...
sw.off() # Light is already off
Example - 2
https://www.udemy.com/course/design-patterns-python/learn/lecture/13682528
from enum import Enum, auto
class State(Enum):
OFF_HOOK = auto()
CONNECTING = auto()
CONNECTED = auto()
ON_HOLD = auto()
ON_HOOK = auto()
class Trigger(Enum):
CALL_DIALED = auto()
HUNG_UP = auto()
CALL_CONNECTED = auto()
PLACED_ON_HOLD = auto()
TAKEN_OFF_HOLD = auto()
LEFT_MESSAGE = auto()
if __name__ == '__main__':
rules = {
State.OFF_HOOK: [
(Trigger.CALL_DIALED, State.CONNECTING)
],
State.CONNECTING: [
(Trigger.HUNG_UP, State.ON_HOOK),
(Trigger.CALL_CONNECTED, State.CONNECTED)
],
State.CONNECTED: [
(Trigger.LEFT_MESSAGE, State.ON_HOOK),
(Trigger.HUNG_UP, State.ON_HOOK),
(Trigger.PLACED_ON_HOLD, State.ON_HOLD)
],
State.ON_HOLD: [
(Trigger.TAKEN_OFF_HOLD, State.CONNECTED),
(Trigger.HUNG_UP, State.ON_HOOK)
]
}
state = State.OFF_HOOK
exit_state = State.ON_HOOK
while state != exit_state:
print(f'The phone is currently {state}')
for i in range(len(rules[state])):
t = rules[state][i][0]
print(f'{i}: {t}')
idx = int(input('Select a trigger:'))
s = rules[state][idx][1]
state = s
print('We are done using the phone.')
8.7 Strategy Design Pattern
Strategy Design Pattern is a behavioral design pattern. It works by abstracting out that part of a class code that is prone to changes into a strategy, which can dynamically be injected at runtime
The Strategy Pattern is similar to the State Pattern
, except that the client passes in the algorithm
that the context should run.
Example - 1
https://www.udemy.com/course/design-patterns-python/learn/lecture/13676780
from abc import ABC
from enum import Enum, auto
class OutputFormat(Enum):
MARKDOWN = auto()
HTML = auto()
# not required but a good idea
class ListStrategy(ABC):
def start(self, buffer): pass
def end(self, buffer): pass
def add_list_item(self, buffer, item): pass
class MarkdownListStrategy(ListStrategy):
def add_list_item(self, buffer, item):
buffer.append(f' * {item}\n')
class HtmlListStrategy(ListStrategy):
def start(self, buffer):
buffer.append('<ul>\n')
def end(self, buffer):
buffer.append('</ul>\n')
def add_list_item(self, buffer, item):
buffer.append(f' <li>{item}</li>\n')
class TextProcessor:
def __init__(self, ):
self.buffer = []
self.list_strategy = None
def append_list(self, items):
self.list_strategy.start(self.buffer)
for item in items:
self.list_strategy.add_list_item(self.buffer, item)
self.list_strategy.end(self.buffer)
def set_output_format(self, output_format):
if output_format == OutputFormat.MARKDOWN:
self.list_strategy = MarkdownListStrategy()
elif output_format == OutputFormat.HTML:
self.list_strategy = HtmlListStrategy()
def clear(self):
self.buffer.clear()
def __str__(self):
return ''.join(self.buffer)
if __name__ == '__main__':
item_list = ['foo', 'bar', 'baz']
tp = TextProcessor()
tp.set_output_format(OutputFormat.MARKDOWN)
tp.append_list(item_list)
print(tp)
tp.clear()
tp.set_output_format(OutputFormat.HTML)
tp.append_list(item_list)
print(tp)
8.8 Template Method Design Pattern
Template Method is a behavioral design pattern that allow us to defines the skeleton
of an algorithm in the superclass
but lets subclasses override
specific steps of the algorithm without changing its structure.
- Template Method Patterns encapsulate algorithms using the
concept of inheritance
. - Strategy Patterns encapsulate algorithms using the
concept of composition
.
Example - 1
The Abstract Class
defines a template method that contains a skeleton
of some algorithm, composed of calls to (usually) abstract primitive operations.
Concrete subclasses
should implement these operations
, but leave the template method
itself intact.
https://refactoring.guru/design-patterns/template-method/python/example
from abc import ABC, abstractmethod
class AbstractClass(ABC):
def template_method(self) -> None:
"""The template method defines the skeleton of an algorithm."""
self.base_operation1()
self.required_operations1()
self.base_operation2()
def base_operation1(self) -> None:
print("AbstractClass says: I am doing the bulk of the work")
def base_operation2(self) -> None:
print("AbstractClass says: But I let subclasses override some operations")
# These operations have to be implemented in subclasses.
@abstractmethod
def required_operations1(self) -> None: pass
class ConcreteClass1(AbstractClass):
def required_operations1(self) -> None:
print("ConcreteClass1 says: Implemented Operation1")
class ConcreteClass2(AbstractClass):
def required_operations1(self) -> None:
print("ConcreteClass2 says: Implemented Operation1")
def client_code(abstract_class: AbstractClass) -> None:
abstract_class.template_method()
if __name__ == "__main__":
client_code(ConcreteClass1())
print("")
client_code(ConcreteClass2())
Example - 2
https://www.udemy.com/course/design-patterns-python/learn/lecture/13676798
from abc import ABC
class Game(ABC):
def __init__(self, number_of_players):
self.number_of_players = number_of_players
self.current_player = 0
def run(self):
self.start()
while not self.have_winner:
self.take_turn()
print(f'Player {self.winning_player} wins!')
def start(self): pass
@property
def have_winner(self): pass
def take_turn(self): pass
@property
def winning_player(self): pass
class Chess(Game):
def __init__(self):
super().__init__(2)
self.max_turns = 10
self.turn = 1
def start(self):
print(f'Starting a game of chess with {self.number_of_players} players.')
@property
def have_winner(self):
return self.turn == self.max_turns
def take_turn(self):
print(f'Turn {self.turn} taken by player {self.current_player}')
self.turn += 1
self.current_player = 1 - self.current_player
@property
def winning_player(self):
return self.current_player
if __name__ == '__main__':
chess = Chess()
chess.run()
8.9 Visitor Design Pattern
Visitor Design Pattern allows adding extra behaviours to entire hierarchies of classes
without modifying every class in the hierarchy.
Visitor isn’t a very common
pattern because of its complexity and narrow applicability
Applicability
Use the Visitor when you need to perform an operation on all elements of a complex object structure (for example, an object tree).
Example - 1
https://sbcode.net/python/visitor/
class Element:
def __init__(self, name, value, parent=None):
self.name = name
self.value = value
self.elements = set()
if parent:
parent.elements.add(self)
def accept(self, visitor):
"""required by the Visitor that will traverse"""
for element in self.elements:
element.accept(visitor)
visitor.visit(self)
# The Client Creating an example object hierarchy.
Element_A = Element("A", 1)
Element_B = Element("B", 2, Element_A)
Element_C = Element("C", 3, Element_A)
Element_D = Element("D", 4, Element_B)
# Now Rather than changing the Element class to support custom operations, we can utilise
# the accept method that was
# implemented in the Element class because of the addition of the IVisitable interface
class PrintElementNamesVisitor:
"""Create a visitor that prints the Element names"""
@staticmethod
def visit(element):
print(element.name)
# Using the PrintElementNamesVisitor to traverse the object hierarchy
Element_A.accept(PrintElementNamesVisitor)
class CalculateElementTotalsVisitor:
"""Create a visitor that totals the Element values"""
total_value = 0
@classmethod
def visit(cls, element):
cls.total_value += element.value
return cls.total_value
# Using the CalculateElementTotalsVisitor to traverse the
# object hierarchy
TOTAL = CalculateElementTotalsVisitor()
Element_A.accept(CalculateElementTotalsVisitor)
print(TOTAL.total_value)