Chain of Responsibility Design Pattern in Java
Understand request handling through an employee leave approval chain.
What is Chain of Responsibility?
The Chain of Responsibility pattern allows an object to pass a request along a chain of handlers. Each handler decides whether to process the request or pass it to the next handler. This decouples the sender from the receivers. In this real-time example, an employee leave request moves from Team Lead to Manager and then Department Head. The first handler that can approve the requested number of days handles it.
Beginner-friendly Java example
Focus on the roles in the example first. The pattern becomes easier when you can identify the sender, receiver, context, state, or strategy involved.
// Step 1: Define the request
class LeaveRequest {
private final String employeeName;
private final int days;
public LeaveRequest(String employeeName, int days) {
this.employeeName = employeeName;
this.days = days;
}
public String getEmployeeName() { return employeeName; }
public int getDays() { return days; }
}
// Step 2: Define the handler interface
interface LeaveApprover {
void approveLeave(LeaveRequest leaveRequest);
}
// Step 3: Implement concrete handlers
class TeamLead implements LeaveApprover {
private static final int MAX_LEAVES_ALLOWED = 2;
private LeaveApprover nextApprover;
public void setNextApprover(LeaveApprover nextApprover) {
this.nextApprover = nextApprover;
}
public void approveLeave(LeaveRequest request) {
if (request.getDays() <= MAX_LEAVES_ALLOWED) {
System.out.println("Approved by Team Lead for "
+ request.getEmployeeName());
} else if (nextApprover != null) {
nextApprover.approveLeave(request);
}
}
}
class Manager implements LeaveApprover {
private static final int MAX_LEAVES_ALLOWED = 5;
private LeaveApprover nextApprover;
public void setNextApprover(LeaveApprover nextApprover) {
this.nextApprover = nextApprover;
}
public void approveLeave(LeaveRequest request) {
if (request.getDays() <= MAX_LEAVES_ALLOWED) {
System.out.println("Approved by Manager for "
+ request.getEmployeeName());
} else if (nextApprover != null) {
nextApprover.approveLeave(request);
}
}
}
class DepartmentHead implements LeaveApprover {
private static final int MAX_LEAVES_ALLOWED = 10;
public void approveLeave(LeaveRequest request) {
if (request.getDays() <= MAX_LEAVES_ALLOWED) {
System.out.println("Approved by Department Head for "
+ request.getEmployeeName());
} else {
System.out.println("Leave denied for "
+ request.getEmployeeName());
}
}
}
// Step 4: Construct and use the chain
public class ChainOfResponsibilityExample {
public static void main(String[] args) {
TeamLead teamLead = new TeamLead();
Manager manager = new Manager();
DepartmentHead departmentHead = new DepartmentHead();
teamLead.setNextApprover(manager);
manager.setNextApprover(departmentHead);
teamLead.approveLeave(new LeaveRequest("John", 3));
teamLead.approveLeave(new LeaveRequest("Alice", 7));
teamLead.approveLeave(new LeaveRequest("Bob", 12));
}
}
// Flow: Team Lead -> Manager -> Department HeadBenefits and trade-offs
Benefits
- Keeps responsibilities focused.
- Reduces conditional and tightly coupled code.
- Makes behavior easier to extend and test.
Trade-offs
- Can introduce extra objects and interfaces.
- Too many small classes can make simple logic harder to follow.
Real-time use cases
- Employee leave approval
- Authentication filters
- Spring Security chains
- Servlet interceptor chains
- Support ticket escalation
Key takeawayUnderstand request handling through an employee leave approval chain. Use it when behavior or communication is changing faster than the objects themselves.
