Unraveling the Design Pattern: Chain of Responsibility

In the realm of software design, the Chain of Responsibility pattern serves as a beacon for creating flexible and decoupled systems. At its core, this pattern revolves around the idea of passing a request through a chain of handlers, each responsible for specific tasks. As we delve into the nuances of this pattern, we discover its ability to promote loose coupling, enabling dynamic and scalable workflows within software architectures.

What?

Chain of responsibility is a behavioural design pattern that allows an object to pass a request along a chain of potential handlers until the request is handled or reaches the end of the chain.

chain of responsibility

Code

# Define the product class
class Product:
    def __init__(self, weight):
        self.weight = weight

# Define the Handler Interface
class ShippingCostHandler:
    def set_next_handler(self, next_handler):
        self.next_handler = next_handler

    def calculate_shipping_cost(self, product):
        pass

# Implement Concrete Handlers
class WeightHandler(ShippingCostHandler):
    def set_next_handler(self, next_handler):
        self.next_handler = next_handler

    def calculate_shipping_cost(self, product):
        if product.weight <= 10:
            shipping_cost = product.weight * 2  # Some calculation logic
            return shipping_cost
        elif self.next_handler:
            return self.next_handler.calculate_shipping_cost(product)
        else:
            return None

class DestinationHandler(ShippingCostHandler):
    def set_next_handler(self, next_handler):
        self.next_handler = next_handler

    def calculate_shipping_cost(self, product):
        if product.destination == "Domestic":
            shipping_cost = product.weight * 5  # Some calculation logic
            return shipping_cost
        elif self.next_handler:
            return self.next_handler.calculate_shipping_cost(product)
        else:
            return None

class ShippingMethodHandler(ShippingCostHandler):
    def set_next_handler(self, next_handler):
        self.next_handler = next_handler

    def calculate_shipping_cost(self, product):
        if product.shipping_method == "Express":
            shipping_cost = product.weight * 10  # Some calculation logic
            return shipping_cost
        elif self.next_handler:
            return self.next_handler.calculate_shipping_cost(product)
        else:
            return None
        
 

# Client Code
product = Product(weight=8)
weight_handler = WeightHandler()
destination_handler = DestinationHandler()
shipping_method_handler = ShippingMethodHandler()

# Set up the chain of responsibility
weight_handler.set_next_handler(destination_handler)
destination_handler.set_next_handler(shipping_method_handler)

# Calculate the shipping cost
shipping_cost = weight_handler.calculate_shipping_cost(product)
print("Shipping Cost:", shipping_cost)

Use Case of the Chain of Responsibility

The Chain of Responsibility pattern finds practical applications in various real-life scenarios where a series of handlers need to process requests or events in a flexible and decoupled manner. Here are a few examples:

  • Event Handling in GUI Frameworks: In graphical user interface (GUI) frameworks, events such as button clicks, key presses, or mouse movements need to be handled by different UI elements. The Chain of Responsibility can be employed to create a chain of event handlers, where each handler is responsible for processing a specific type of event. This allows for the dynamic addition or removal of handlers without modifying the existing codebase.
  • Logging Systems: In logging frameworks, different loggers may be responsible for handling logs of varying severity levels. The Chain of Responsibility pattern can be used to create a chain of loggers, each capable of handling logs up to a certain severity threshold. This enables a flexible and configurable logging system where logs are processed and filtered based on their severity.
  • Middleware in Web Development: In web development, middleware components often handle various aspects of request processing, such as authentication, logging, or caching. The Chain of Responsibility can be applied to create a middleware pipeline where each component in the chain processes the request or response and passes control to the next middleware in the sequence. This allows for the dynamic composition of middleware and easy extension of the request-handling process.
  • Workflow Systems: Workflow systems that involve multiple stages of processing or approval can benefit from the Chain of Responsibility pattern. Each stage in the workflow can be represented by a handler, and the chain defines the sequence in which these stages are executed. This design accommodates changes in the workflow, such as adding new processing steps or modifying the order of existing ones, without necessitating extensive modifications.
  • Security Access Control: In security systems, access control policies may involve multiple levels of authorization. The Chain of Responsibility can be applied to model a hierarchy of authorization handlers, where each handler verifies specific aspects of access control. This allows for a flexible and scalable approach to managing permissions and accommodating changes in authorization rules.

By embracing the Chain of Responsibility pattern in these real-life scenarios, developers can create systems that are not only modular and maintainable but also capable of adapting to changing requirements with ease.

Conclusion

In essence, the Chain of Responsibility pattern stands as a design paradigm that champions flexibility and scalability in software architectures. By orchestrating a dynamic chain of handlers, each dedicated to specific responsibilities, the pattern fosters loose coupling and facilitates seamless modifications and extensions. Its adherence to the Single Responsibility Principle ensures modular and maintainable code, while the ability to adjust processing workflows at runtime provides a crucial edge in adapting to evolving requirements. With real-world applications ranging from event handling systems to graphical user interfaces, the Chain of Responsibility pattern remains a cornerstone in crafting resilient, adaptable software solutions that transcend the constraints of static, monolithic structures.

Resources

For further exploration, make sure to check out these helpful resources:

Leave a Comment