logo

Achieve Ultimate Excellence

Chain of Responsibility - Assign next handler Dynamically at Runtime

Introduction

The Chain of Responsibility is a behavioral design pattern that promotes a loose coupling between sender and receiver objects, allowing multiple objects to handle a request without explicitly specifying the recipient. This pattern forms a chain of handler objects, where each handler has the ability to process the request or pass it along to the next handler in the chain. This ensures that the request is processed by the appropriate handler dynamically at runtime.

How it Works

In the Chain of Responsibility pattern, a series of handler objects are linked together to form a chain. When a request is made, it is passed through the chain until a suitable handler is found. Each handler has a reference to the next handler in the chain, creating a linked list-like structure. If a handler cannot process the request, it forwards the request to the next handler in the chain.

Benefits

  • Decouples sender and receiver objects, promoting flexibility and maintainability.

  • Allows handlers to be added or removed without affecting the client code.

  • Encourages single responsibility and promotes code reusability.

Example: Implementing the Chain of Responsibility in Java

Use Case

In our example, we'll implement a logging system using the Chain of Responsibility pattern. The system will have three types of loggers: ConsoleLogger, FileLogger, and EmailLogger. Each logger will have a different logging level: INFO, DEBUG, and ERROR, respectively. The loggers will form a chain, and a log request will be passed through the chain until an appropriate logger handles it.

1. Define the Logger Interface

public interface Logger {
    void setNextLogger(Logger nextLogger);
    void logMessage(LogLevel level, String message);
}

The Logger interface defines two methods: setNextLogger() to set the next logger in the chain and logMessage() to log a message with a specific log level.

2. Implement the Concrete Loggers

public class ConsoleLogger implements Logger {
    private Logger nextLogger;

    @Override
    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

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

public class FileLogger implements Logger {
    private Logger nextLogger;

    @Override
    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    @Override
    public void logMessage(LogLevel level, String message) {
        if (level == LogLevel.DEBUG) {
            // Log to file
            System.out.println("File Logger: " + message);
        } else if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
}

public class EmailLogger implements Logger {
    private Logger nextLogger;

    @Override
    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    @Override
    public void logMessage(LogLevel level, String message) {
        if (level == LogLevel.ERROR) {
            // Send email notification
            System.out.println("Email Logger: " + message);
        } else if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
}

The ConsoleLogger, FileLogger, and EmailLogger classes are concrete implementations of the Logger interface. Each logger checks if it can handle the log level; if not, it passes the request to the next logger in the chain.

3. Define Logging Levels

public enum LogLevel {
    INFO,
    DEBUG,
    ERROR
}

The LogLevel enum defines the different log levels: INFO, DEBUG, and ERROR.

4. Client Code - Using the Chain

public class Client {
    public static void main(String[] args) {
        // Create loggers
        Logger consoleLogger = new ConsoleLogger();
        Logger fileLogger = new FileLogger();
        Logger emailLogger = new EmailLogger();

        // Form the chain
        consoleLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(emailLogger);

        // Log messages
        consoleLogger.logMessage(LogLevel.INFO, "This is an informational message.");
        consoleLogger.logMessage(LogLevel.DEBUG, "This is a debug message.");
        consoleLogger.logMessage(LogLevel.ERROR, "An error occurred!");
    }
}

The Client class sets up the chain of responsibility by linking the loggers together. It then logs messages using the consoleLogger. The messages will be processed by the appropriate logger in the chain based on their log levels.

Output

Console Logger: This is an informational message.
File Logger: This is a debug message.
Email Logger: An error occurred!

Conclusion

The Chain of Responsibility design pattern allows us to build a flexible and extensible system where multiple objects can handle requests in a dynamic and organized manner. By implementing a chain of handlers, we achieve loose coupling and promote reusability, making our code more maintainable and adaptable to changes. This pattern is a powerful tool in the developer's arsenal for creating modular and efficient systems.

avatar
Article By,
Create by
Browse Articles by Related Categories
Browse Articles by Related Tags
Share Article on:

Related posts

Related posts