Chain of Responsibility Pattern in TypeScript
The Chain of Responsibility pattern is a behavioral design pattern that allows an object to pass a request along a chain of potential handlers. Each handler decides either to process the request or to pass it along to the next handler in the chain. This pattern promotes loose coupling and flexibility in handling requests.
Class-Based Implementation
Let's start by exploring a class-based implementation of the Chain of Responsibility pattern in TypeScript. Consider a scenario where we want to handle different levels of logging: Info, Warning, and Error.
// Handler interface
interface Logger {
setNext(logger: Logger): Logger;
log(message: string, level: string): void;
}
// Abstract base handler
abstract class AbstractLogger implements Logger {
private nextLogger: Logger | null = null;
setNext(logger: Logger): Logger {
this.nextLogger = logger;
return logger;
}
log(message: string, level: string): void {
if (this.shouldHandle(level)) {
this.write(message);
}
if (this.nextLogger) {
this.nextLogger.log(message, level);
}
}
protected abstract shouldHandle(level: string): boolean;
protected abstract write(message: string): void;
}
// Concrete Handlers
class InfoLogger extends AbstractLogger {
protected shouldHandle(level: string): boolean {
return level === 'INFO';
}
protected write(message: string): void {
console.log(`Info: ${message}`);
}
}
class WarningLogger extends AbstractLogger {
protected shouldHandle(level: string): boolean {
return level === 'WARNING';
}
protected write(message: string): void {
console.warn(`Warning: ${message}`);
}
}
class ErrorLogger extends AbstractLogger {
protected shouldHandle(level: string): boolean {
return level === 'ERROR';
}
protected write(message: string): void {
console.error(`Error: ${message}`);
}
}
// Usage
const infoLogger = new InfoLogger();
const warningLogger = new WarningLogger();
const errorLogger = new ErrorLogger();
infoLogger.setNext(warningLogger).setNext(errorLogger);
infoLogger.log('Application started', 'INFO');
infoLogger.log('Some warning message', 'WARNING');
infoLogger.log('Critical error occurred', 'ERROR');
In this example, each logger class extends the AbstractLogger
, implementing the shouldHandle
and write
methods. The chain is set up using the setNext
method, allowing the handlers to be linked together.
Pure Function Implementation
Now, let's explore a pure function implementation of the Chain of Responsibility pattern. We'll use function composition and currying to achieve a similar result.
type Logger = (message: string, level: string) => void;
const createLogger = (
shouldHandle: (level: string) => boolean,
write: (message: string) => void
): Logger => (message, level) => {
if (shouldHandle(level)) {
write(message);
}
};
// Usage
const infoLogger: Logger = createLogger(
(level) => level === 'INFO',
(message) => console.log(`Info: ${message}`)
);
const warningLogger: Logger = createLogger(
(level) => level === 'WARNING',
(message) => console.warn(`Warning: ${message}`)
);
const errorLogger: Logger = createLogger(
(level) => level === 'ERROR',
(message) => console.error(`Error: ${message}`)
);
const logChain: Logger = (message, level) => {
infoLogger(message, level);
warningLogger(message, level);
errorLogger(message, level);
};
// Usage
logChain('Application started', 'INFO');
logChain('Some warning message', 'WARNING');
logChain('Critical error occurred', 'ERROR');
In this functional approach, we create logger functions using the createLogger
function, which returns a logger function based on the provided shouldHandle
and write
functions. The logChain
function is composed by calling each logger function in sequence.
Conclusion
The Chain of Responsibility pattern, whether implemented using classes or pure functions, provides a flexible way to handle requests sequentially. Choosing between class-based or functional implementation depends on the specific requirements and design preferences of your project.