Design patterns: Chain of Responsibility

Mauto 3 min

The Chain of Responsibility Pattern is a behavioral design pattern that lets multiple objects handle a request without the sender knowing which specific object will process it. The objects are linked in a chain, and each object either handles the request or passes it on to the next one in the chain. This pattern decouples issuing a request from handling it, making it more flexible to wire components together and allowing chains to be changed without touching the client code.

Strengths

  1. No more complex conditionals: It avoids nested, complex conditional structures (if-else or switch), improving code readability and maintainability.
  2. Flexibility in defining the handling sequence: Handlers can be combined flexibly and dynamically, making it easy to change the order in which requests are processed without modifying the client logic.
  3. Separation between sender and receiver: The sender of the request doesn’t know the final handler, and handling can be delegated along the chain, encouraging adherence to the single responsibility principle.
  4. Incremental changes: It’s easy to add or remove handlers without modifying existing code, making the system easy to extend and maintain.
  5. Component reuse: Handlers can be reused in other chains or contexts, encouraging code reuse and reducing duplication.

Use Case

Suppose we want to implement a handling chain for a technical support request process, where different support tiers (for example, basic level, advanced level and administrator) handle requests based on their complexity.

PHP Code

// Handler interface
interface SupportHandler {
    public function setNext(SupportHandler $handler): SupportHandler;
    public function handle(string $issue): void;
}

// Concrete Handler for Basic Support
class BasicSupportHandler implements SupportHandler {
    private $nextHandler;

    public function setNext(SupportHandler $handler): SupportHandler {
        $this->nextHandler = $handler;
        return $handler;
    }

    public function handle(string $issue): void {
        if ($issue === "basic") {
            echo "Basic support is handling the issue.\n";
        } elseif ($this->nextHandler) {
            $this->nextHandler->handle($issue);
        }
    }
}

// Concrete Handler for Advanced Support
class AdvancedSupportHandler implements SupportHandler {
    private $nextHandler;

    public function setNext(SupportHandler $handler): SupportHandler {
        $this->nextHandler = $handler;
        return $handler;
    }

    public function handle(string $issue): void {
        if ($issue === "advanced") {
            echo "Advanced support is handling the issue.\n";
        } elseif ($this->nextHandler) {
            $this->nextHandler->handle($issue);
        }
    }
}

// Concrete Handler for Admin Support
class AdminSupportHandler implements SupportHandler {
    private $nextHandler;

    public function setNext(SupportHandler $handler): SupportHandler {
        $this->nextHandler = $handler;
        return $handler;
    }

    public function handle(string $issue): void {
        if ($issue === "admin") {
            echo "Admin support is handling the issue.\n";
        } elseif ($this->nextHandler) {
            $this->nextHandler->handle($issue);
        }
    }
}

// Usage
$basicHandler = new BasicSupportHandler();
$advancedHandler = new AdvancedSupportHandler();
$adminHandler = new AdminSupportHandler();

// Building the chain of responsibility
$basicHandler->setNext($advancedHandler)->setNext($adminHandler);

// Issue a request
echo "Issue: basic\n";
$basicHandler->handle("basic");

echo "\nIssue: advanced\n";
$basicHandler->handle("advanced");

echo "\nIssue: admin\n";
$basicHandler->handle("admin");

echo "\nIssue: unknown\n";
$basicHandler->handle("unknown");

Explanation

  • BasicSupportHandler, AdvancedSupportHandler and AdminSupportHandler are the different handlers that deal with the various levels of support issues.
  • Each handler decides whether it can handle the request or passes it on to the next one in the chain, which is set via the setNext() method.
  • If no handler deals with a request, it is simply ignored, or you can add a fallback.

Conclusion

The Chain of Responsibility Pattern is ideal for scenarios where requests need to be processed flexibly by a series of potentially independent handlers. It makes the system more extensible and easier to maintain, reducing code complexity and improving readability.

BACK TO THE GUIDE

Copiato