The Chain of Responsibility pattern is a behavioural design pattern that allows an object to pass a request along a chain of potential handlers until one of the handlers processes the request. This pattern decouples the sender and receiver of the request, providing flexibility in how requests are handled.

Explanation:

Purpose:

To avoid coupling the sender of a request to its receiver by giving multiple objects a chance to handle the request.

Components:

Scenario 1:

Consider a customer support system where different support levels (like front-line support, supervisor, and manager) can handle different types of requests.

// Abstract Handler
class Handler {
  constructor() {
    this.nextHandler = null;
  }
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  handleRequest(request) {
    if (this.nextHandler) {
      return this.nextHandler.handleRequest(request);
    }
    return null;
  }
}

// Concrete Handlers
class FrontLineSupport extends Handler {
  handleRequest(request) {
    if (request.type === 'simple') {
      return `FrontLineSupport handled request: ${request.content}`;
    }
    return super.handleRequest(request);
  }
}

class SupervisorSupport extends Handler {
  handleRequest(request) {
    if (request.type === 'intermediate') {
      return `SupervisorSupport handled request: ${request.content}`;
    }
    return super.handleRequest(request);
  }
}

class ManagerSupport extends Handler {
  handleRequest(request) {
    if (request.type === 'complex') {
      return `ManagerSupport handled request: ${request.content}`;
    }
    return super.handleRequest(request);
  }
}

// Client Code
const frontLine = new FrontLineSupport();
const supervisor = new SupervisorSupport();
const manager = new ManagerSupport();
frontLine.setNext(supervisor).setNext(manager);

const requests = [{
    type: 'simple',
    content: 'Password reset'
  },
  {
    type: 'intermediate',
    content: 'Software installation'
  },
  {
    type: 'complex',
    content: 'Server outage'
  },
];
requests.forEach((request) => {
  const response = frontLine.handleRequest(request);
  console.log(response);
});

In the implementation of the Chain of Responsibility pattern, we start with an abstract handler class that defines a method setNext for setting the next handler in the chain and a method handleRequest for processing the request. We then have three concrete handlers: FrontLineSupport, SupervisorSupport, and ManagerSupport, each of which implements the handleRequest method to handle specific types of requests.

If a handler cannot process a request, it calls super.handleRequest(request) to pass the request to the next handler in the chain. In the client code, we set up the chain by linking the handlers together using the setNext method.

Finally, we create an array of requests, each with a different type and content, and pass each request to the first handler in the chain (frontLine). The request travels through the chain until it finds a handler that can process it, and the appropriate handler's response is logged to the console. This setup allows for a flexible and decoupled request handling mechanism, which can be easily extended by adding more handlers.

Scenario 2:

Let’s implement a system for processing different types of documents (like text documents, spreadsheets, and presentations) that might require various levels of processing (basic validation, in-depth analysis, and final approval).

// Abstract Handler
class DocumentHandler {
  constructor() {
    this.nextHandler = null;
  }
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  handleRequest(document) {
    if (this.nextHandler) {
      return this.nextHandler.handleRequest(document);
    }
    return null;
  }
}

// Concrete Handlers
class BasicValidationHandler extends DocumentHandler {
  handleRequest(document) {
    if (document.isValid()) {
      console.log(`BasicValidationHandler: ${document.name} passed basic validation.`);
      return super.handleRequest(document);
    } else {
      return `BasicValidationHandler: ${document.name} failed basic validation.`;
    }
  }
}

class InDepthAnalysisHandler extends DocumentHandler {
  handleRequest(document) {
    if (document.needsAnalysis()) {
      console.log(`InDepthAnalysisHandler: ${document.name} is being analyzed.`);
      document.analyze();
      return super.handleRequest(document);
    } else {
      return `InDepthAnalysisHandler: ${document.name} does not need in-depth
analysis.`;
    }
  }
}

class ApprovalHandler extends DocumentHandler {
  handleRequest(document) {
    if (document.isApproved()) {
      return `ApprovalHandler: ${document.name} has already been approved.`;
    } else {
      console.log(`ApprovalHandler: ${document.name} is pending approval.`);
      document.approve();
      return `ApprovalHandler: ${document.name} has been approved.`;
    }
  }
}

// Document class to simulate different types of documents
class Document {
  constructor(name, type) {
    this.name = name;
    this.type = type;
    this.valid = true;
    this.analyzed = false;
    this.approved = false;
  }
  isValid() {
    return this.valid;
  }
  needsAnalysis() {
    return !this.analyzed;
  }
  analyze() {
    this.analyzed = true;
  }
  isApproved() {
    return this.approved;
  }
  approve() {
    this.approved = true;
  }
}

// Client Code
const basicValidation = new BasicValidationHandler();
const inDepthAnalysis = new InDepthAnalysisHandler();
const approval = new ApprovalHandler();
basicValidation.setNext(inDepthAnalysis).setNext(approval);

const documents = [
  new Document('Document1', 'text'),
  new Document('Document2', 'spreadsheet'),
  new Document('Document3', 'presentation'),
];

documents.forEach((document) => {
  const response = basicValidation.handleRequest(document);
  console.log(response);
});

In this implementation, we start with an abstract handler class DocumentHandler that defines the setNext method for linking handlers and the handleRequest method for processing requests. We then define three concrete handlers: BasicValidationHandler, InDepthAnalysisHandler, and ApprovalHandler. Each handler checks specific conditions related to the document and processes it accordingly. If a handler can't fully process the request, it passes it to the next handler in the chain.