Chain of Responsibility Pattern
The Chain of Responsibility pattern decouples request senders from receivers by passing requests along a chain of handlers. Each handler either processes the request or forwards it to the next handler in the chain. This pattern is useful for scenarios like approval workflows, logging systems, or HTTP interceptors.
Structure
- Handler: Declares an interface for handling requests and optionally defines a method to set the next handler.
- ConcreteHandler: Implements the handling logic and decides whether to process the request or delegate it.
- Client: Initiates the request to the first handler in the chain.
Example Implementation
Consider an approval system with three levels: Supervisor, DepartmentManager, and GeneralManager. Each can approve requests based on a priority level.
abstract class Approver {
protected Approver nextApprover;
public void setNext(Approver approver) {
this.nextApprover = approver;
}
public abstract void processRequest(int priority);
}
class Supervisor extends Approver {
@Override
public void processRequest(int priority) {
if (priority <= 1) {
System.out.println("Supervisor approved the request.");
} else if (nextApprover != null) {
System.out.println("Supervisor escalated the request.");
nextApprover.processRequest(priority);
}
}
}
class DepartmentManager extends Approver {
@Override
public void processRequest(int priority) {
if (priority <= 2) {
System.out.println("Department Manager approved the request.");
} else if (nextApprover != null) {
System.out.println("Department Manager escalated the request.");
nextApprover.processRequest(priority);
}
}
}
class GeneralManager extends Approver {
@Override
public void processRequest(int priority) {
System.out.println("General Manager handled the request (approved or denied)."));
}
}
Usage
public class ChainDemo {
public static void main(String[] args) {
Approver supervisor = new Supervisor();
Approver deptMgr = new DepartmentManager();
Approver genMgr = new GeneralManager();
supervisor.setNext(deptMgr);
deptMgr.setNext(genMgr);
supervisor.processRequest(1); // Approved by Supervisor
supervisor.processRequest(3); // Escalated to General Manager
}
}
Pros and Cons
Advantages:
- Reduces coupling between sender and receivers.
- Enhances flexibility in assigning responsibilities.
- Easy to add new handlers.
Disadvantages:
- No guarantee that a request will be handled.
- Long chains may impact performance and readability.
Command Pattern
The Command pattern encapsulates a request as an object, there by allowing parameterization of clients with queues, requests, and operations. It supports undoable operations and logging.
Structure
- Command: Declares an interface for executing an operation.
- ConcreteCommand: Binds a receiver to an action and implements
execute()by invoking the corresponding method on the receiver. - Invoker: Holds and triggers command objects.
- Receiver: Knows how to perform the actual work.
Example Implementation
A student receives commands from teachers—e.g., clean classroom or do homework—but can only execute one due to time constraints.
class Student {
public void cleanRoom(String name) {
System.out.println(name + " is cleaning the classroom.");
}
public void finishHomework(String name) {
System.out.println(name + " is finishing homework.");
}
}
interface TaskCommand {
void execute(String studentName);
}
class CleanRoomCommand implements TaskCommand {
private Student student;
public CleanRoomCommand(Student student) {
this.student = student;
}
@Override
public void execute(String name) {
student.cleanRoom(name);
}
}
class HomeworkCommand implements TaskCommand {
private Student student;
public HomeworkCommand(Student student) {
this.student = student;
}
@Override
public void execute(String name) {
student.finishHomework(name);
}
}
class TeacherInvoker {
private TaskCommand primaryTask;
public void assignTask(TaskCommand command) {
if (primaryTask == null) {
primaryTask = command;
} else {
System.out.println("Ignoring additional task due to time limit.");
}
}
public void executeAssignedTask(String name) {
if (primaryTask != null) {
primaryTask.execute(name);
}
}
}
Usage
public class CommandDemo {
public static void main(String[] args) {
Student student = new Student();
TeacherInvoker invoker = new TeacherInvoker();
invoker.assignTask(new CleanRoomCommand(student));
invoker.assignTask(new HomeworkCommand(student)); // Ignored
invoker.executeAssignedTask("Alex");
}
}
Pros and Cons
Advantages:
- Decouples invoker from receiver.
- Supports queuing, logging, and undo operations.
- Easy to extend with new commmands.
Disadvantages:
- Can lead to many small classes.
- Overhead when commands are simple.