Chain of Responsibility Pattern




Chain of Responsibility is a behavioral design pattern that allows an object to pass a request along a chain of handlers. Each handler can process the request or pass it to the next in the chain. This pattern is useful for decoupling handlers and receivers, making the system more flexible and scalable.

🔗 Do you like Techelopment? Check out the site for all the details!


Pattern Family: Behavioral Patterns

The Chain of Responsibility pattern belongs to the Behavioral Pattern family. Behavioral patterns focus on how objects interact and communicate with each other. They help define how responsibilities are distributed among classes and how objects cooperate while maintaining loose coupling. Other patterns in this family include Observer, Strategy, Command, and Template Method.


Diagram



How the Chain of Responsibility Pattern Works

The Chain of Responsibility pattern works by creating a linked list of handlers, where each handler can process a request or pass it on to the next. This allows multiple objects to process a request without the sender needing to know which one will handle it. This also allows for separate implementation logic, creating decoupling and keeping the code more modular and maintainable.

Let's see a simple example in JavaScript to better understand how it works:

// Handler Interface
class ValidationHandler {
    setNextHandler(handler) {
        this.nextHandler = handler;
    }

    handleRequest(regex) {
        throw new Error("This method should be overridden!");
    }
}

// Concrete Handlers
class Validation1 extends ValidationHandler {
    handleRequest(request) {
        if (request.message && request.message !== "") {
            console.log("Not empty validation OK")            
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

class Validation2 extends ValidationHandler {
   
    handleRequest(request) {
        const regex = /[0-9]+/;

        if (!regex.test(request.message)) {
            console.log("Validation not digit OK");
             
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

class Validation3 extends ValidationHandler {
    handleRequest(request) {
        const MINIMUM_LENGTH = 10;
        if (request.message.length > MINIMUM_LENGTH) {
            console.log(`Validation minimum length ${MINIMUM_LENGTH} OK`);
            if (this.nextHandler) {
                this.nextHandler.handleRequest(request);
            }
        }
    }
}

// Execution (Client) Code
const validation1 = new Validation1();
const validation2 = new Validation2();
const validation3 = new Validation3();

validation1.setNextHandler(validation2);
validation2.setNextHandler(validation3);

const messageToValidate = { message: "Hello, World!" }
validation1.handleRequest(messageToValidate);

How it works:

  • The first handler (Validation1) checks that the message is not empty before passing it to the next handler.

  • The second handler (Validation2) checks that the message does not contain digits; if it is valid, it forwards it.

  • The third handler (Validation3) checks that the message has a minimum length before passing it on.

  • This approach ensures that each validation is applied sequentially and that the request is fully validated only if it passes all checks.

This way we can add new validations simply by implementing a new class that extends ValidationHandler and implementing the handleRequest method with the validation code to be executed. Then we just need to add an object of the new class to the chain (hence the name Chain of Responsibility): validation3.setNextHandler(validation4).

A more concrete example: Implementing a Wizard in JavaScript

Let's now consider a practical use case for the Chain of Responsibility pattern: managing a multi-step form or wizard. This pattern allows us to dynamically manage the steps of the form by modifying the nextHandler without having to rewrite the entire step management logic. This flexibility makes it easy to add, remove, or rearrange steps, keeping the code clean and modular.

The wizard consists of the following steps:
  1. Insert your personal data: entering a user's personal data

  2. Upload your photo: uploading a photo

  3. Confirm data: confirmation of the data entered

  4. Outcome: outcome of the operation

Each step will be a handler in the chain, either processing the request or passing it on.

class WizardStep {
    setNext(step) {
        this.next = step;
        return step;
    }
   
    handle(request) {
        throw new Error("This method should be overridden!");
    }

    process(request) {
        if (this.next) {
            return this.next.handle(request);
        }
    }
}

class PersonalDataStep extends WizardStep {
    handle(request) {
        console.log("Step 1: Insert your personal data");

        //...code to manage personal data collection
       
        request.personalDataInserted = true;
        super.process(request);
    }
}

class UploadPhotoStep extends WizardStep {
    handle(request) {
        if (!request.personalDataInserted) {
            console.log("Error: Personal data must be inserted before uploading a photo.");
            return;
        }
        console.log("Step 2: Load your photo");
         
        //...manage photo uploading

        request.photoUploaded = true;
        super.process(request);
    }
}

class ConfirmDataStep extends WizardStep {
    handle(request) {
        console.log("Step 3: Confirm data");
         
        //...confirmation logic goes here

        request.dataConfirmed = true;
        super.process(request);
    }
}

class OutcomeStep extends WizardStep {
    handle(request) {
        if (!request.dataConfirmed) {
            console.log("Error: Data must be confirmed before sending outcome.");
            return;
        }
        console.log("Step 4: Outcome");
        request.outcomeSent = true;
        super.process(request);
    }
}


// Setting up the chain
const step1 = new PersonalDataStep();
const step2 = new UploadPhotoStep();
const step3 = new ConfirmDataStep();
const step4 = new OutcomeStep();

step1.setNext(step2).setNext(step3).setNext(step4);

// Running the wizard
console.log("Starting the wizard...");
const request = {};
step1.handle(request);
console.log("Wizard completed.");

Explanation

  1. Each step extends the WizardStep base class and implements its own handle method.

  2. The setNext() method is used to dynamically and clearly create the chain.

  3. The chain ensures that steps are executed in the correct order by preventing a step from being executed if the previous one has not been completed.


Benefits of Using the Chain of Responsibility Pattern

  • It decouples request senders from receivers, making the code more modular.

  • Flexible management logic, allowing you to change steps dynamically.

  • Greater maintainability, thanks to the isolation of logic in separate classes.

By applying the Chain of Responsibility model, we ensure that each step of the wizard is executed in the correct order, while maintaining clean and reusable logic.



Follow me #techelopment

Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
telegram: @techelopment_channel
whatsapp: Techelopment
youtube: @techelopment