Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Implementing Chain of Responsibility and Command Patterns for Flexible Request Handling

Notes 5

Behavioral design patterns focus on the interactions and responsibility distribution between objects, offering optimal ways to define their behaviors and duties. This discussion covers the Chain of Responsibility and Command patterns.

Chain of Responsibility Pattern

Definition

The Chain of Responsibility pattern decouples request senders from receivers by allowing multiple objects the opportunity to process a request. The request is passed along a chain of handler objects until one handles it, creating a flexible linkage where senders don't need to know the specific receiver.

Problem Statement

Software systems often encounter scenarios where a request might be processed by any of several objects, with the specific handler determined only at runtime. Directly coupling senders to receivers makes systems rigid and difficult to extend.

Implementation Approach

The pattern addresses this through three key components:

  1. Abstract Handler: Defines the interface for handling requests and maintains a reference to the next handler in the chain.
  2. Concrete Handlers: Implement the handling interface, processing requests they're responsible for or passing them along the chain.
  3. Chain Assembly: Handlers are dynamically linked at runtime to form processing chains.
#include <iostream>
#include <string>

// Abstract handler interface
class RequestProcessor {
protected:
    RequestProcessor* successor;

public:
    void SetSuccessor(RequestProcessor* next) {
        successor = next;
    }

    virtual void ProcessRequest(int priority, const std::string& content) = 0;
};

// Concrete handler for low-priority requests
class LowPriorityHandler : public RequestProcessor {
public:
    void ProcessRequest(int priority, const std::string& content) override {
        if (priority >= 1 && priority <= 3) {
            std::cout << "LowPriority handled: " << content << std::endl;
        }
        if (successor != nullptr) {
            successor->ProcessRequest(priority, content);
        }
    }
};

// Concrete handler for medium-priority requests
class MediumPriorityHandler : public RequestProcessor {
public:
    void ProcessRequest(int priority, const std::string& content) override {
        if (priority >= 4 && priority <= 7) {
            std::cout << "MediumPriority handled: " << content << std::endl;
        }
        if (successor != nullptr) {
            successor->ProcessRequest(priority, content);
        }
    }
};

// Client implementation
int main() {
    RequestProcessor* lowHandler = new LowPriorityHandler();
    RequestProcessor* mediumHandler = new MediumPriorityHandler();
    
    lowHandler->SetSuccessor(mediumHandler);

    // Process various requests
    lowHandler->ProcessRequest(2, "Routine system check");
    lowHandler->ProcessRequest(5, "User authentication request");
    lowHandler->ProcessRequest(8, "Critical system alert");

    delete lowHandler;
    delete mediumHandler;
    return 0;
}

Use Cases

  • Multiple potential handlers for a request with runtime determination
  • Requests submitted to one of several objects without explicit receiver specification
  • Systems requiring dynamic handler addition or removal

Advantages and Disadvantages

Advantages:

  • Reduces coupling between request senders and receivers
  • Simplifies object connections by eliminating explicit receiver specification
  • Enables dynamic runtime modification of handler chains

Disadvantages:

  • Requests may traverse the entire chain without being processed
  • Performance overhead from sequential handler evaluations
  • Debugging complexity increases with chain length

Command Pattern

Definition

The Command pattern encapsulates requests as objects, enabling parameterization of objects with different requests, queues, or logging. It supports undo operations and completely decouples request senders from receivers through command objects.

Problem Statement

Systems often need to decouple request senders from receivers to allow new receivers without modifying sender code, or new commands without changing receiver implementations. Support for undo/redo operations adds further complexity.

Implementation Approach

The pattern structures solutions through four components:

  1. Command Interface: Declares execution methods
  2. Concrete Commands: Implement the interface and associate with receiver objects
  3. Invoker: Triggers command eexcution
  4. Receiver: Performs the actual operations
#include <iostream>
#include <memory>
#include <vector>

// Receiver interface
class Device {
public:
    virtual void PerformOperation() = 0;
    virtual ~Device() {}
};

// Concrete receiver: Light
class Light : public Device {
public:
    void PerformOperation() override {
        std::cout << "Light turned on" << std::endl;
    }
};

// Command interface
class DeviceCommand {
protected:
    std::shared_ptr<Device> targetDevice;

public:
    DeviceCommand(std::shared_ptr<Device> device) : targetDevice(device) {}
    virtual void Execute() = 0;
    virtual ~DeviceCommand() {}
};

// Concrete command: Activate light
class LightOnCommand : public DeviceCommand {
public:
    LightOnCommand(std::shared_ptr<Device> device) : DeviceCommand(device) {}
    void Execute() override {
        targetDevice->PerformOperation();
    }
};

// Invoker: Control panel
class ControlPanel {
private:
    std::vector<std::shared_ptr<DeviceCommand>> commandSlots;

public:
    void AssignCommand(int position, std::shared_ptr<DeviceCommand> cmd) {
        if (position >= 0 && position < commandSlots.size()) {
            commandSlots[position] = cmd;
        }
    }

    void TriggerCommand(int position) {
        if (position >= 0 && position < commandSlots.size() && 
            commandSlots[position] != nullptr) {
            commandSlots[position]->Execute();
        }
    }

    void AddCommand(std::shared_ptr<DeviceCommand> cmd) {
        commandSlots.push_back(cmd);
    }
};

// Client implementation
int main() {
    auto roomLight = std::make_shared<Light>();
    auto lightCommand = std::make_shared<LightOnCommand>(roomLight);

    ControlPanel panel;
    panel.AddCommand(lightCommand);
    panel.TriggerCommand(0); // Output: Light turned on

    return 0;
}

Use Cases

  • Abstracting executable actions with specific receivers
  • Submitting requests without explicit receiver specification
  • Implementing undo/redo functionality
  • Maintaining sender-receiver decoupling

Advantages and Disadvantages

Advantages:

  • Decouples request senders from receivers
  • Facilitates easy addition of new commands
  • Naturally supports undo/redo operations

Disadvantages:

  • Can lead to class proliferation with many command-receiver pairs
  • Increases system complexity through additional abstraction layers
  • Introduces performance overhead from multiple object instantiations

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.