What is the Chain of Responsibility pattern?

The Chain of Responsibility pattern is a behavioral design pattern that allows an object to pass a request along a chain of potential handlers until the request is handled by an appropriate handler. It decouples the sender of the request from its receivers, providing a way to process a request dynamically without explicitly knowing which object will handle it.

In the Chain of Responsibility pattern, each handler in the chain has a reference to its successor (next handler) and can decide whether to handle the request or pass it to the next handler in the chain. This creates a hierarchical chain of objects where each object has a chance to handle the request or delegate it further down the chain.

Key components of the Chain of Responsibility pattern:

  1. Handler: The handler is an interface or an abstract class that defines the common interface for handling requests. It usually declares a method like handleRequest().
  2. Concrete Handler: The concrete handler is a specific implementation of the handler interface. It handles requests that it is responsible for and can pass the request to the next handler in the chain if needed. It maintains a reference to the next handler in the chain.
  3. Client: The client is responsible for initiating the request and creating the chain of handlers. It sends the request to the first handler in the chain and does not have knowledge of which handler will handle the request.

Benefits and use cases of the Chain of Responsibility pattern:

  1. Decoupling sender and receiver: The Chain of Responsibility pattern decouples the sender of the request from its receivers. The sender does not need to know which object will handle the request, and the receivers are not tightly coupled to each other.
  2. Dynamic handling of requests: The pattern allows for dynamic handling of requests based on the runtime configuration of the chain. Handlers can be added, removed, or reordered without impacting the client code.
  3. Flexible and extensible design: The Chain of Responsibility pattern provides flexibility and extensibility in handling requests. New handlers can be added to the chain without modifying the existing code, allowing for easy customization and extension of the handling logic.
  4. Multiple handling options: The pattern allows for multiple objects to have an opportunity to handle a request. Each handler can decide whether to handle the request, pass it to the next handler, or terminate the chain if the request cannot be handled.
  5. Reduced coupling: The Chain of Responsibility pattern helps reduce coupling between sender and receiver by promoting a design where each object has a single responsibility. Each handler in the chain focuses on handling specific types of requests, making the system more modular and maintainable.

Example of the Chain of Responsibility pattern in Java:

// Handler interface
interface Handler {
    void setNext(Handler next);
    void handleRequest(Request request);
}

// Concrete handler
class ConcreteHandler1 implements Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE1) {
            System.out.println("ConcreteHandler1 handles request: " + request);
        } else if (next != null) {
            next.handleRequest(request);
        }
    }
}

// Concrete handler
class ConcreteHandler2 implements Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE2) {
            System.out.println("ConcreteHandler2 handles request: " + request);
        } else if (next != null) {
            next.handleRequest(request);
        }
    }
}

// Concrete handler
class ConcreteHandler3 implements Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public void handleRequest(Request request) {
        if (request.getType() == RequestType.TYPE3) {
            System.out.println("ConcreteHandler3 handles request: " + request);
        } else if (next != null) {
            next.handleRequest(request);
        }
    }
}

// Request class
class Request {
    private RequestType type;

    public Request(RequestType type) {
        this.type = type;
    }

    public RequestType getType() {
        return type;
    }

    @Override
    public String toString() {
        return "Request{" +
                "type=" + type +
                '}';
    }
}

// Request type enum
enum RequestType {
    TYPE1, TYPE2, TYPE3
}

// Usage:
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
Handler handler3 = new ConcreteHandler3();

handler1.setNext(handler2);
handler2.setNext(handler3);

Request request1 = new Request(RequestType.TYPE1);
Request request2 = new Request(RequestType.TYPE2);
Request request3 = new Request(RequestType.TYPE3);

handler1.handleRequest(request1);
handler1.handleRequest(request2);
handler1.handleRequest(request3);

In the above example, the Chain of Responsibility pattern is used to handle different types of requests. The Handler interface defines the methods for setting the next handler and handling requests. The ConcreteHandler1, ConcreteHandler2, and ConcreteHandler3 classes are concrete handlers that implement the handling logic for specific types of requests.

In the client code, a chain of handlers is created and configured by setting the next handler using the setNext() method. Requests are then sent to the first handler in the chain using the handleRequest() method. Each handler checks if it can handle the request, and if not, it passes the request to the next handler in the chain until the request is handled or the chain ends.

error: Content is protected !!