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.