LLD - Design a Logger - Chain of Responsibility Design Pattern

LLD - Design a Logger - Chain of Responsibility Design Pattern

Understanding Chain of Responsibility Design Pattern

This is a Behavioural Design Pattern where the request is passed through a series of handlers & each handler decides either to process the request or to pass it to the next handler in the chain.

Use the Chain of Responsibility Pattern when you want to give more than one object a chance to handle a request - Head First Design Patterns Book

Request Handling - Customer Support

Explanation:

Case 1: Customer requests to exchange the package as the product delivered was damaged. Basic Customer Support could handle this at Level 1 itself.

Case 2: Customer cannot log in to the app due to some technical issue. This could not be handled at Level 1 so the request will be redirected to Level 2 for Tech Support.

Case 3: The customer has some critical claims like losing the high-value order shipment that could be a potential legal issue. Now this type of request can’t be handled at Level 1 or Level 2 and needs to be redirected to Level 3 itself.

Case 4: If there is a customer issue that is not being handled at lower levels, then it will need the involvement of Senior Managers at the Managerial Level to provide better solutions.

Blueprint of Chain of Responsibility Design Pattern

  1. Abstract Class or Handler Interface
    - Declares a method to handle the request - handleRequest()
    - Holds a reference to the next handler in the chain

  2. Concrete Handlers
    - Implements the handleRequest()
    - If this Handler is not able to process the request, then it will pass to the next handler in the chain

  3. Client
    - Responsible for creating and linking handlers together
    - Sends a request to the first or level 1 handler

Case Study - Design a Logger

Design a logging system for an application that can process log messages of different severity levels like INFO, WARNING, ERROR

UML Diagram

Solution

Defining the Log Level as an enum

enum LogLevel {
    INFO,
    WARNING,
    ERROR
}

Defining the Logger Handler abstract class with handleRequest

abstract class LoggerHandler {
    protected LoggerHandler nextHandler;

    // Constructor injection for next handler
    public LoggerHandler(LoggerHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleRequest(LogLevel level, String message);
}

Defining Concrete Logger Handlers with their implementation of abstract method

class InfoLogHandler extends LoggerHandler {
    public InfoLogHandler(LoggerHandler nextHandler) {
        super(nextHandler);
    }

    @Override
    public void handleRequest(LogLevel level, String message) {
        if (level == LogLevel.INFO) {
            System.out.println("INFO: " + message);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(level, message);
        }
    }
}

class WarningLogHandler extends LoggerHandler {
    public WarningLogHandler(LoggerHandler nextHandler) {
        super(nextHandler);
    }

    @Override
    public void handleRequest(LogLevel level, String message) {
        if (level == LogLevel.WARNING) {
            System.out.println("WARNING: " + message);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(level, message);
        }
    }
}

class ErrorLogHandler extends LoggerHandler {
    public ErrorLogHandler(LoggerHandler nextHandler) {
        super(nextHandler);
    }

    @Override
    public void handleRequest(LogLevel level, String message) {
        if (level == LogLevel.ERROR) {
            System.out.println("ERROR: " + message);
        }
    }
}

Client

public class LoggerSystem {
    public static void main(String[] args) {
        // Set up the chain: Info -> Warning -> Error
        LoggerHandler logger = new InfoLogger(new WarningLogger(new ErrorLogger(null)));

        /*
        OR, we can setup the chain like this
        LoggerHandler errorLogger = new ErrorLogger(null);           // Last in chain
        LoggerHandler warningLogger = new WarningLogger(errorLogger); // Middle in chain
        LoggerHandler logger = new InfoLogger(warningLogger);     // First in chain
        */

        // Generate some log messages
        System.out.println("Logging an info message:");
        logger.handleRequest(LogLevel.INFO, "This is an info message");

        System.out.println("Logging a warning message:");
        logger.handleRequest(LogLevel.WARNING, "This is a warning message");

        System.out.println("Logging an error message:");
        logger.handleRequest(LogLevel.ERROR, "This is an error message");
    }
}

Here's the complete code on GitHub: Design Logger

Benefits

  1. Decoupling - The client is decoupled from the handling logic

  2. Flexibility - Easy to add, remove, or reorder handlers in the chain