12 min read

In this article by Sakis Kasampalis, author of the book Mastering Python Design Patterns, we will see a detailed description of the Chain of Responsibility design pattern with the help of a real-life example as well as a software example. Also, its use cases and implementation are discussed.

(For more resources related to this topic, see here.)

When developing an application, most of the time we know which method should satisfy a particular request in advance. However, this is not always the case. For example, we can think of any broadcast computer network, such as the original Ethernet implementation [j.mp/wikishared]. In broadcast computer networks, all requests are sent to all nodes (broadcast domains are excluded for simplicity), but only the nodes that are interested in a sent request process it. All computers that participate in a broadcast network are connected to each other using a common medium such as the cable that connects the three nodes in the following figure:

Penetration Testing with Raspberry Pi

If a node is not interested or does not know how to handle a request, it can perform the following actions:

  • Ignore the request and do nothing
  • Forward the request to the next node

The way in which the node reacts to a request is an implementation detail. However, we can use the analogy of a broadcast computer network to understand what the chain of responsibility pattern is all about. The Chain of Responsibility pattern is used when we want to give a chance to multiple objects to satisfy a single request, or when we don’t know which object (from a chain of objects) should process a specific request in advance. The principle is the same as the following:

  1. There is a chain (linked list, tree, or any other convenient data structure) of objects.
  2. We start by sending a request to the first object in the chain.
  3. The object decides whether it should satisfy the request or not.
  4. The object forwards the request to the next object.
  5. This procedure is repeated until we reach the end of the chain.

At the application level, instead of talking about cables and network nodes, we can focus on objects and the flow of a request. The following figure, courtesy of a title=”Scala for Machine Learning” www.sourcemaking.com [j.mp/smchain], shows how the client code sends a request to all processing elements (also known as nodes or handlers) of an application:

Penetration Testing with Raspberry Pi

Note that the client code only knows about the first processing element, instead of having references to all of them, and each processing element only knows about its immediate next neighbor (called the successor), not about every other processing element. This is usually a one-way relationship, which in programming terms means a singly linked list in contrast to a doubly linked list; a singly linked list does not allow navigation in both ways, while a doubly linked list allows that. This chain organization is used for a good reason. It achieves decoupling between the sender (client) and the receivers (processing elements) [GOF95, page 254].

A real-life example

ATMs and, in general, any kind of machine that accepts/returns banknotes or coins (for example, a snack vending machine) use the chain of responsibility pattern. There is always a single slot for all banknotes, as shown in the following figure, courtesy of www.sourcemaking.com:

Penetration Testing with Raspberry Pi

When a banknote is dropped, it is routed to the appropriate receptacle. When it is returned, it is taken from the appropriate receptacle [j.mp/smchain], [j.mp/c2chain]. We can think of the single slot as the shared communication medium and the different receptacles as the processing elements. The result contains cash from one or more receptacles. For example, in the preceding figure, we see what happens when we request $175 from the ATM.

A software example

I tried to find some good examples of Python applications that use the Chain of Responsibility pattern but I couldn’t, most likely because Python programmers don’t use this name. So, my apologies, but I will use other programming languages as a reference.

The servlet filters of Java are pieces of code that are executed before an HTTP request arrives at a target. When using servlet filters, there is a chain of filters. Each filter performs a different action (user authentication, logging, data compression, and so forth), and either forwards the request to the next filter until the chain is exhausted, or it breaks the flow if there is an error (for example, the authentication failed three consecutive times) [j.mp/soservl].

Apple’s Cocoa and Cocoa Touch frameworks use Chain of Responsibility to handle events. When a view receives an event that it doesn’t know how to handle, it forwards the event to its superview. This goes on until a view is capable of handling the event or the chain of views is exhausted [j.mp/chaincocoa].

Use cases

By using the Chain of Responsibility pattern, we give a chance to a number of different objects to satisfy a specific request. This is useful when we don’t know which object should satisfy a request in advance. An example is a purchase system. In purchase systems, there are many approval authorities. One approval authority might be able to approve orders up to a certain value, let’s say $100. If the order is more than $100, the order is sent to the next approval authority in the chain that can approve orders up to $200, and so forth.

Another case where Chain of Responsibility is useful is when we know that more than one object might need to process a single request. This is what happens in an event-based programming. A single event such as a left mouse click can be caught by more than one listener.

It is important to note that the Chain of Responsibility pattern is not very useful if all the requests can be taken care of by a single processing element, unless we really don’t know which element that is. The value of this pattern is the decoupling that it offers. Instead of having a many-to-many relationship between a client and all processing elements (and the same is true regarding the relationship between a processing element and all other processing elements), a client only needs to know how to communicate with the start (head) of the chain.

The following figure demonstrates the difference between tight and loose coupling. The idea behind loosely coupled systems is to simplify maintenance and make it easier for us to understand how they function [j.mp/loosecoup]:

Penetration Testing with Raspberry Pi

Implementation

There are many ways to implement Chain of Responsibility in Python, but my favorite implementation is the one by Vespe Savikko [j.mp/savviko]. Vespe’s implementation uses dynamic dispatching in a Pythonic style to handle requests [j.mp/ddispatch].

Let’s implement a simple event-based system using Vespe’s implementation as a guide. The following is the UML class diagram of the system:

Penetration Testing with Raspberry Pi

The Event class describes an event. We’ll keep it simple, so in our case an event has only name:

class Event: 
    def __init__(self, name): 
        self.name = name 

    def __str__(self): 
        return self.name

The Widget class is the core class of the application. The parent aggregation shown in the UML diagram indicates that each widget can have a reference to a parent object, which by convention, we assume is a Widget instance. Note, however, that according to the rules of inheritance, an instance of any of the subclasses of Widget (for example, an instance of MsgText) is also an instance of Widget. The default value of parent is None:

class Widget: 
    def __init__(self, parent=None): 
        self.parent = parent

The handle() method uses dynamic dispatching through hasattr() and getattr() to decide who is the handler of a specific request (event). If the widget that is asked to handle an event does not support it, there are two fallback mechanisms. If the widget has parent, then the handle() method of parent is executed. If the widget has no parent but a handle_default() method, handle_default() is executed:

def handle(self, event): 
        handler = 'handle_{}'.format(event) 
        if hasattr(self, handler): 
            method = getattr(self, handler) 
            method(event) 
        elif self.parent: 
            self.parent.handle(event) 
        elif hasattr(self, 'handle_default'): 
            self.handle_default(event)

At this point, you might have realized why the Widget and Event classes are only associated (no aggregation or composition relationships) in the UML class diagram. The association is used to show that the Widget class “knows” about the Event class but does not have any strict references to it, since an event needs to be passed only as a parameter to handle().

MainWIndow, MsgText, and SendDialog are all widgets with different behaviors. Not all these three widgets are expected to be able to handle the same events, and even if they can handle the same event, they might behave differently. MainWIndow can handle only the close and default events:

class MainWindow(Widget): 
    def handle_close(self, event): 
        print('MainWindow: {}'.format(event)) 

    def handle_default(self, event): 
        print('MainWindow Default: {}'.format(event))

SendDialog can handle only the paint event:

class SendDialog(Widget): 
    def handle_paint(self, event): 
        print('SendDialog: {}'.format(event))

Finally, MsgText can handle only the down event:

class MsgText(Widget): 
    def handle_down(self, event): 
        print('MsgText: {}'.format(event))

The main() function shows how we can create a few widgets and events, and how the widgets react to those events. All events are sent to all the widgets. Note the parent relationship of each widget. The sd object (an instance of SendDialog) has as its parent the mw object (an instance of MainWindow). However, not all objects need to have a parent that is an instance of MainWindow. For example, the msg object (an instance of MsgText) has the sd object as a parent:

def main(): 
    mw = MainWindow() 
    sd = SendDialog(mw) 
    msg = MsgText(sd) 

    for e in ('down', 'paint', 'unhandled', 'close'): 
        evt = Event(e) 
        print('nSending event -{}- to MainWindow'.format(evt)) 
        mw.handle(evt) 
        print('Sending event -{}- to SendDialog'.format(evt)) 
        sd.handle(evt) 
        print('Sending event -{}- to MsgText'.format(evt)) 
        msg.handle(evt)

The following is the full code of the example (chain.py):

class Event: 
    def __init__(self, name): 
        self.name = name 

    def __str__(self): 
        return self.name 

class Widget: 
    def __init__(self, parent=None): 
        self.parent = parent 

    def handle(self, event): 
        handler = 'handle_{}'.format(event) 
        if hasattr(self, handler): 
            method = getattr(self, handler) 
            method(event) 
        elif self.parent: 
            self.parent.handle(event) 
        elif hasattr(self, 'handle_default'): 
            self.handle_default(event) 

class MainWindow(Widget): 
    def handle_close(self, event): 
        print('MainWindow: {}'.format(event)) 

    def handle_default(self, event): 
        print('MainWindow Default: {}'.format(event)) 

class SendDialog(Widget): 
    def handle_paint(self, event): 
        print('SendDialog: {}'.format(event)) 

class MsgText(Widget): 
    def handle_down(self, event): 
        print('MsgText: {}'.format(event)) 

def main(): 
    mw = MainWindow() 
    sd = SendDialog(mw) 
    msg = MsgText(sd) 

    for e in ('down', 'paint', 'unhandled', 'close'): 
        evt = Event(e) 
        print('nSending event -{}- to MainWindow'.format(evt)) 
        mw.handle(evt) 
        print('Sending event -{}- to SendDialog'.format(evt)) 
        sd.handle(evt) 
        print('Sending event -{}- to MsgText'.format(evt)) 
        msg.handle(evt) 

if __name__ == '__main__': 
    main()

Executing chain.py gives us the following results:

>>> python3 chain.py

Sending event -down- to MainWindow
MainWindow Default: down
Sending event -down- to SendDialog 
MainWindow Default: down 
Sending event -down- to MsgText 
MsgText: down 

Sending event -paint- to MainWindow 
MainWindow Default: paint 
Sending event -paint- to SendDialog 
SendDialog: paint 
Sending event -paint- to MsgText 
SendDialog: paint 

Sending event -unhandled- to MainWindow 
MainWindow Default: unhandled 
Sending event -unhandled- to SendDialog 
MainWindow Default: unhandled 
Sending event -unhandled- to MsgText 
MainWindow Default: unhandled 

Sending event -close- to MainWindow 
MainWindow: close 
Sending event -close- to SendDialog 
MainWindow: close 
Sending event -close- to MsgText 
MainWindow: close

There are some interesting things that we can see in the output. For instance, sending a down event to MainWindow ends up being handled by the default MainWindow handler. Another nice case is that although a close event cannot be handled directly by SendDialog and MsgText, all the close events end up being handled properly by MainWindow. That’s the beauty of using the parent relationship as a fallback mechanism.

If you want to spend some more creative time on the event example, you can replace the dumb print statements and add some actual behavior to the listed events. Of course, you are not limited to the listed events. Just add your favorite event and make it do something useful!

Another exercise is to add a MsgText instance during runtime that has MainWindow as the parent. Is this hard? Do the same for an event (add a new event to an existing widget). Which is harder?

Summary

In this article, we covered the Chain of Responsibility design pattern. This pattern is useful to model requests / handle events when the number and type of handlers isn’t known in advance. Examples of systems that fit well with Chain of Responsibility are event-based systems, purchase systems, and shipping systems.

In the Chain Of Responsibility pattern, the sender has direct access to the first node of a chain. If the request cannot be satisfied by the first node, it forwards to the next node. This continues until either the request is satisfied by a node or the whole chain is traversed. This design is used to achieve loose coupling between the sender and the receiver(s).

ATMs are an example of Chain Of Responsibility. The single slot that is used for all banknotes can be considered the head of the chain. From here, depending on the transaction, one or more receptacles is used to process the transaction. The receptacles can be considered the processing elements of the chain.

Java’s servlet filters use the Chain of Responsibility pattern to perform different actions (for example, compression and authentication) on an HTTP request. Apple’s Cocoa frameworks use the same pattern to handle events such as button presses and finger gestures.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here