You are currently viewing C++ Design Patterns: Chain of Responsibility

C++ Design Patterns: Chain of Responsibility

In the world of software development, design patterns are like tried-and-true blueprints for solving common problems. One such pattern is the Chain of Responsibility, a behavioral design strategy that efficiently manages requests by passing them along a line of potential handlers until one is found that can deal with the request. This pattern is especially handy when you need a group of objects to be able to process a request without any of them needing to know who the others are or how they work.

Imagine the Chain of Responsibility pattern as being similar to how customer service calls are handled. When you call customer support, your call might be passed from one representative to another, each with a different level of expertise, until your problem is resolved. Each representative has a chance to address the issue, but if it’s not within their ability to solve, they pass it on to someone else who might know more.

In this article, we’re going to dive into how this pattern can be applied in C++. We’ll break down the concept with a practical example and provide complete, easy-to-understand code examples so that even beginners can grasp how to implement this useful pattern in their own projects.

Understanding the Chain of Responsibility Pattern

The Chain of Responsibility pattern is a design strategy in C++ and other programming languages that elegantly manages requests by passing them through a series of handler objects. This approach mirrors real-world scenarios like a customer service hotline where different representatives handle requests based on their complexity or nature.

Key Components of the Chain of Responsibility

  • Handler: At the core of this pattern is the Handler interface. It sets out a blueprint for handling requests and optionally passing them along the line. Each handler has a method that attempts to process a request. If it’s not equipped to handle the request, it passes it on to the next handler in the chain, termed as its successor.
  • Concrete Handler: These are specific classes that implement the Handler interface. Each class contains the actual logic needed to address the requests it receives. If a Concrete Handler can take care of the request, it will do so; otherwise, it delegates the responsibility to the next handler in line.
  • Client: This is the starting point of the request. It sends the request to the first handler, setting off the chain reaction of processing attempts across the handlers.

Benefits of Using the Chain of Responsibility

  • Decoupling Request Sender and Receiver: One of the primary advantages is the decoupling of the request sender (Client) from the receiver (Handlers). This means the Client does not need to know which part of the code will handle its request, making the system more modular and easier to maintain.
  • Dynamic Reconfiguration: The pattern allows for dynamic reconfiguration of handlers. Handlers can be added, removed, or reordered without changing the client or other handlers. This flexibility is crucial for applications needing adaptable and scalable solutions.
  • Flexibility in Handling Requests: The pattern supports having multiple objects handle a request, enhancing flexibility. A request can be processed by one handler or shared among several, depending on the complexity and requirements. This flexibility also allows for more robust error handling and response generation.

By separating concerns and minimizing dependencies, the Chain of Responsibility pattern ensures that C++ applications are easier to extend and maintain. It also helps in creating a clear and manageable structure, where different handlers can be tasked with handling various operations, making the system adaptable and efficient.

Implementing the Chain of Responsibility Pattern in C++

Let’s dive into a practical example to understand the Chain of Responsibility pattern better. Imagine a software licensing system where various types of license checks are required depending on the complexity or level of service requested by the user. This system is perfect for illustrating how this pattern can simplify decision-making processes in software design.

Define the Handler Interface

The first step is to create the backbone of our pattern, the Handler interface. This interface will lay out the structure that all concrete handlers must follow. It’s somewhat like creating a blueprint for the tasks each handler will perform.

#include <iostream>
#include <memory>

class Handler {

protected:
    std::shared_ptr<Handler> successor;  // This will hold the next handler in the chain

public:
    // Set the next handler in the chain
    void setSuccessor(std::shared_ptr<Handler> successor) {
        this->successor = successor;
    }

    // Each handler will implement this method to process the request
    virtual void handleRequest(int request) = 0;

    // A virtual destructor to ensure proper cleanup of derived classes
    virtual ~Handler() {}
};

Create Concrete Handlers

With our interface in place, we can now develop specific handlers. Each handler addresses a different range of license requests based on the complexity or features requested by the software user. This step effectively differentiates what each type of license can handle.

class FreeLicenseHandler : public Handler {

public:
    void handleRequest(int request) override {
	
        if (request <= 10) {  // Simple requests handled by the free license
            std::cout << "Handled by Free License Handler with request level " << request << std::endl;
        } else if (successor) {  // Pass more complex requests along the chain
            successor->handleRequest(request);
        }
		
    }
	
};

class StandardLicenseHandler : public Handler {

public:
    void handleRequest(int request) override {
	
        if (request > 10 && request <= 20) {  // Moderately complex requests
            std::cout << "Handled by Standard License Handler with request level " << request << std::endl;
        } else if (successor) {
            successor->handleRequest(request);
        }
		
    }
	
};

class PremiumLicenseHandler : public Handler {

public:
    void handleRequest(int request) override {
	
        if (request > 20) {  // Highly complex requests
            std::cout << "Handled by Premium License Handler with request level " << request << std::endl;
        } else {
            std::cout << "Request level " << request << " not supported, requiring higher licensing." << std::endl;
        }
		
    }
	
};

Setup the Chain and Use it

Finally, we establish the chain of handlers and test the system with various request levels to see how each part of the chain handles different scenarios.

int main() {

    std::shared_ptr<Handler> freeHandler = std::make_shared<FreeLicenseHandler>();
    std::shared_ptr<Handler> standardHandler = std::make_shared<StandardLicenseHandler>();
    std::shared_ptr<Handler> premiumHandler = std::make_shared<PremiumLicenseHandler>();

    // Set up the chain of responsibility
    freeHandler->setSuccessor(standardHandler);
    standardHandler->setSuccessor(premiumHandler);

    // Send different requests to see who handles them
    freeHandler->handleRequest(5);   // Should be handled by the Free License Handler
    freeHandler->handleRequest(15);  // Should be handled by the Standard License Handler
    freeHandler->handleRequest(25);  // Should be handled by the Premium License Handler
    freeHandler->handleRequest(30);  // Should indicate unsupported request level

    return 0;
}

This implementation illustrates how the Chain of Responsibility pattern can facilitate the processing of requests in various complexities, providing a clear pathway for escalation and processing without intertwining the objects. Each part of the system focuses solely on its capability, promoting not just clarity and maintenance but also making the system robust and scalable.

Conclusion

The Chain of Responsibility pattern is a powerful tool in programming that helps manage requests by passing them through a series of handlers or processors. It’s like having a line of customer service representatives where each one has a specific task. If the first representative can’t help you, your request is passed on to the next one, and so on, until you get the help you need.

In our C++ example with a software licensing system, this pattern allows us to handle different kinds of license requests efficiently. Each type of license—free, standard, or premium—has its own handler that knows exactly what to do with it. This organized approach avoids any messy overlap between handlers and ensures that the system is easy to maintain and modify.

By learning and applying the Chain of Responsibility pattern, you can make your software designs more adaptable and easier to manage. Whether you’re dealing with simple or complex systems, this pattern can help you keep your code clean and your project extendable, which is a big win for any programmer.

Leave a Reply