Chain Of Responsibility

Chain of Responsibility Pattern

Introduction

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

Purpose

  • To decouple the sender of a request from its receivers by giving more than one object a chance to handle the request.
  • To pass requests along a chain of handlers.

Use Cases

  • When more than one object may handle a request, and the handler is not known in advance.
  • When the set of objects that can handle a request should be specified dynamically.
  • To reduce coupling and give more flexibility in distributing responsibilities among objects.

Advantages/Disadvantages

Advantages:

  • Decouples request senders and receivers.
  • Simplifies your object because it doesn’t have to know the chain’s structure and keep direct references to its members.
  • Enhances flexibility in assigning responsibilities to objects.

Disadvantages:

  • A request can end up unhandled.
  • Performance can suffer due to the cumulative processing of the chain.

Implementation in JavaScript

javascript
	class Handler {
	  setNext(handler) {
	    this.nextHandler = handler;
	    return handler;
	  }
	
	  handle(request) {
	    if (this.nextHandler) {
	      return this.nextHandler.handle(request);
	    }
	  }
	}
	
	class ConcreteHandler1 extends Handler {
	  handle(request) {
	    if (request === 'handle1') {
	      return `ConcreteHandler1: Handled ${request}`;
	    } else {
	      return super.handle(request);
	    }
	  }
	}
	
	class ConcreteHandler2 extends Handler {
	  handle(request) {
	    if (request === 'handle2') {
	      return `ConcreteHandler2: Handled ${request}`;
	    } else {
	      return super.handle(request);
	    }
	  }
	}
	
	// Usage
	const handler1 = new ConcreteHandler1();
	const handler2 = new ConcreteHandler2();
	
	handler1.setNext(handler2);
	
	console.log(handler1.handle('handle2')); // "ConcreteHandler2: Handled handle2"

Practical Example

In a backend application, you might have a series of middleware functions that process incoming HTTP requests. Each middleware can process the request, stop the chain, or pass the request to the next middleware.

javascript
	class Middleware {
	  setNext(middleware) {
	    this.next = middleware;
	    return middleware;
	  }
	
	  handle(req) {
	    if (this.next) {
	      return this.next.handle(req);
	    }
	  }
	}
	
	class AuthenticationMiddleware extends Middleware {
	  handle(req) {
	    if (!req.user.isAuthenticated) {
	      console.log('Authentication required.');
	      return;
	    }
	    return super.handle(req);
	  }
	}
	
	class LoggingMiddleware extends Middleware {
	  handle(req) {
	    console.log(`Logging request: ${req.url}`);
	    return super.handle(req);
	  }
	}
	
	// Usage
	const authMiddleware = new AuthenticationMiddleware();
	const loggingMiddleware = new LoggingMiddleware();
	
	authMiddleware.setNext(loggingMiddleware);
	
	const request = { url: '/api/data', user: { isAuthenticated: true } };
	authMiddleware.handle(request);

In this example, AuthenticationMiddleware checks if the user is authenticated, while LoggingMiddleware logs the request. The request goes through the chain, with each middleware performing its specific task.