×

Chain of Responsibility – Design Pattern with Python

Chain Of Responsibility Design Patterns Python Feature

In an application, there will be several interfaces, working together, performing their specified task. Now if any entity(interface) of the application wants series of tasks to be done, performed by various interfaces, How would that entity call all interface’s objects?

  • Calling each interface object one by one can be a option, but it will increase the code complexity.

This is where the Chain Of Responsibility Design Pattern helps. Chain Of Responsibility pattern suggests designing an interface to create a chain of objects (like:- linked list or a tree data structure).

Entities instead of calling separate objects will call the chain.

  • Entity will start with calling the first object in the chain.
  • The object decides whether it would satisfy the request or not.
  • The request will be forwarded to the next object, regardless of the fact whether the previous object completed the request or not.
  • This procedure will be repeated until calling request reaches the last object in the chain.

The Chain Of responsibility Design Pattern is classified under Behavioural Design Patterns as it discusses the ways to handle communication between objects.

Elements of Chain Of Responsibility pattern:

  • Abstract Handler: Absract handler will be the abstract base calss for all the concrete interfaces(i.e. concrete handlers) and is inherited by all Concrete Handlers.
class AbstractHandler:
    def __init__():
        pass
    @abstractmethod
    def handle(request):
        pass
    @abstractmethod
    def processRequest(request):
        pass
  • Concrete Handlers: Each concrete handler inherits from the abstract handler, have a handle() method, inherited from base class, which provides logic to handle requests and ProcessRequest() method to call the next object.
class ConcreteHandlerOne(AbstractHandler):
    def processRequest(request):
        pass
 
class ConcreteHandlerTwo(AbstractHandler):
    def processRequest(request):
        pass
  • Default Handler: Default handlers process the request in case if all the concrete handlers have failed to respond to the request.
class DefaultHandler(AbstractHandler):
    def processRequest(request):
        pass
  • Client: Entity or interface that calls for a request(s).
class Client:
    def __init__():
        pass
 
    def call(requests):
        pass

Example:

Let’s say we creating a game, that has some Creatures. Creatures that have properties like attack and Defence.

class Creature:
    def __init__(self, name, attack, defence):
           self.name = name
           self.attack = attack
           self.defence = defence

    def __str__(self):
        return f'{self.name}, ({self.attack}/{self.defence})' 

And, in-game we want to change attack and defence properties. Now, let’s add an interface that an entity can call upon – to modify those properties. Since there will be a number of interfaces.

Therefore, lets first add an interface to create the Chain of objects:

class CreatureModifier:
    """Abstract Handler"""
    def __init__(self, creature:Creature):
        self.creature = creature
        self.next_modifier = None
    
    """Method adding objects into the chain. """
    def add_modifier(self, modifier):
        if self.next_modifier:
            self.next_modifier.add_modifier(modifier)
        else:
            self.next_modifier = modifier
   
    """Method to call objects in the chain. """
    def handle(self):
        if self.next_modifier:
            self.next_modifier.handle()
  • add_modifier() method serves as a link between adjacent objects.
  • handle() method calls the next object in the chain.

Now the actual interfaces for modifying Creature’s properties:

  • DoubleAttack() method doubles the creatures attacking power. Modifiers will be called by the abstract handler.
class DoubleAttack(CreatureModifier):
    """Concrete Handlers"""
    def handle(self):
        print(f'Doubling {self.creature.name}\'s attack.')
        self.creature.attack *= 2
        super().handle()
  • DoubleDefence() method to double creatures defeence power.
class DoubleDefence(CreatureModifier):
    """Concrete Handlers"""
    def handle(self):
        print(f'Doubling {self.creature.name}\'s defence.')
        self.creature.defence *= 2
        super().handle()

The flow of the application will be like this:

  • First, we will create the Creatures object.
  • Second, we will call Creature Modifiers to create a chain, having one node. Node will consist of creatures object and next_modifier, a pointer to point next object.
  • Third, we will call add_modifier() method with Concrete handlers(i.e. DoubleAttack, and DoubleDefence) objects to add new nodes in the chain.
  • At last, we will call the handle method that will start calling all the objects in the chain.

The driver code:

if __name__ == '__main__':
    goblin = Creature('Goblin', 1, 1)
    print(goblin)
    
    """root is the client """
    root = CreatureModifier(goblin)

    root.add_modifier(DoubleAttack(goblin))
    root.add_modifier(DoubleDefence(goblin))

    root.handle()
    print(goblin)

Output:

Goblin, (1/1)
Doubling Goblin's attack.
Doubling Goblin's defence.
Goblin, (2/2)

Chain of Responsibility Pattern allows an entity(or interface or object) to send requests without knowing which object will handle the request.

Pros and Cons of Chain of Responsibility Design Pattern

Pros

  • Decouples the coupling between sender and receiver entities.
  • Makes entities more flexible to implement, redesign and reuse.

Cons

  • Recursive calls to objects in the chain may degrade application performance.