Chain of Responsibility Pattern

Pass a request along a chain of handlers, each processing the request or passing it to the next handler

Understanding the Chain of Responsibility Pattern

The Chain of Responsibility pattern is a behavioral design pattern that passes a request along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

Problem

Imagine you're developing an authentication and authorization system for a web application. The system needs to perform several checks before granting access:

Each of these checks is independent but must be executed in a specific order. Adding or removing checks should not disrupt the system.

Solution

The Chain of Responsibility pattern suggests organizing these checks into a chain of objects, each with a reference to the next check in the chain. The request enters at the beginning of the chain and is passed along until a handler processes it or until it reaches the end without being handled.

Structure

Chain of Responsibility Pattern Structure

Participants

When to Use

Use the Chain of Responsibility Pattern when:

Benefits

Real-World Uses

Implementation Example

Here's a JavaScript implementation of the Chain of Responsibility pattern for a request validation system:

// Handler interface (abstract class in JavaScript)
class Handler {
    setNext(handler) {
        this.nextHandler = handler;
        return handler;
    }
    
    handle(request) {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        
        return null; // If there's no next handler
    }
}

// Concrete Handlers
class AuthenticationHandler extends Handler {
    handle(request) {
        // Check if the request has valid authentication
        if (!request.token) {
            return "AuthenticationHandler: Request lacks authentication token";
        }
        
        console.log("AuthenticationHandler: Request has a token, passing to next handler");
        return super.handle(request);
    }
}

class AuthorizationHandler extends Handler {
    handle(request) {
        // Check if the user has the permission
        if (!request.permissions || !request.permissions.includes('access')) {
            return "AuthorizationHandler: User doesn't have access permission";
        }
        
        console.log("AuthorizationHandler: User has permission, passing to next handler");
        return super.handle(request);
    }
}

class ValidationHandler extends Handler {
    handle(request) {
        // Check if the request data is valid
        if (!request.data || Object.keys(request.data).length === 0) {
            return "ValidationHandler: Request has no data";
        }
        
        // Additional validation logic could go here
        
        console.log("ValidationHandler: Request data is valid, passing to next handler");
        return super.handle(request);
    }
}

class RateLimitHandler extends Handler {
    constructor() {
        super();
        this.requestCount = 0;
        this.maxRequests = 5;
    }
    
    handle(request) {
        this.requestCount++;
        
        if (this.requestCount > this.maxRequests) {
            return "RateLimitHandler: Too many requests, try again later";
        }
        
        console.log(`RateLimitHandler: Request count ${this.requestCount}/${this.maxRequests}, passing to next handler`);
        return super.handle(request);
    }
}

class ResourceHandler extends Handler {
    handle(request) {
        // This is the final handler that processes the actual request
        return `ResourceHandler: Request processed successfully, fetched data for ID: ${request.data.id}`;
    }
}

// Client code
function clientCode() {
    // Create handler instances
    const authentication = new AuthenticationHandler();
    const authorization = new AuthorizationHandler();
    const validation = new ValidationHandler();
    const rateLimit = new RateLimitHandler();
    const resource = new ResourceHandler();
    
    // Build the chain
    authentication
        .setNext(authorization)
        .setNext(validation)
        .setNext(rateLimit)
        .setNext(resource);
    
    // Create some sample requests
    const validRequest = {
        token: "valid-token",
        permissions: ["access", "read"],
        data: { id: 123 }
    };
    
    const noTokenRequest = {
        permissions: ["access"],
        data: { id: 456 }
    };
    
    const noPermissionRequest = {
        token: "valid-token",
        permissions: ["read"],
        data: { id: 789 }
    };
    
    const noDataRequest = {
        token: "valid-token",
        permissions: ["access", "read"]
    };
    
    // Process the requests
    console.log("Processing valid request:");
    console.log(authentication.handle(validRequest));
    console.log("\nProcessing request without token:");
    console.log(authentication.handle(noTokenRequest));
    console.log("\nProcessing request without permission:");
    console.log(authentication.handle(noPermissionRequest));
    console.log("\nProcessing request without data:");
    console.log(authentication.handle(noDataRequest));
    
    // Test rate limiting by sending multiple valid requests
    console.log("\nTesting rate limiting:");
    for (let i = 0; i < 6; i++) {
        console.log(`Request ${i + 1}: ${authentication.handle(validRequest)}`);
    }
}

clientCode();

In this example:

Interactive Demo

Experience the Chain of Responsibility pattern in action with this interactive demo of a request processing system. See how requests flow through different handlers and how each handler makes independent decisions.

Interactive Demo: Request Processing Chain

Create requests with different characteristics and see how they're processed by various handlers in the chain.

Build Your Request

Authentication
Waiting...
Authorization
Waiting...
Validation
Waiting...
Rate Limiting
Waiting...
Resource
Waiting...

Current Request

{ "token": "valid-token", "permissions": ["access", "read"], "data": { "id": 123 } }
Chain of Responsibility demo initialized