Mastering the Chain of Responsibility Pattern: From Flawed IF Chains to Elegant Handlers
This article explains the Chain of Responsibility design pattern, shows why naïve nested if‑statements are problematic, demonstrates a step‑by‑step refactor using linked handlers, introduces an abstract handler hierarchy, and finally presents a factory‑based dynamic configuration for scalable request processing in Java.
Background
The author describes a recent experience where a team member implemented an import feature using the Chain of Responsibility pattern, resulting in overly complex code with many bugs, and argues that a Template Method would have been more appropriate.
What Is the Chain of Responsibility?
The Chain of Responsibility is a behavioral design pattern that lets you pass a request along a chain of handlers; each handler can either process the request or forward it to the next handler.
Usage Scenarios
Multi‑condition flow control such as permission checks
ERP workflow approvals (e.g., manager, HR manager, project manager)
Underlying implementation of Java filters
Bad Example (Nested IFs)
A game with three levels is implemented using nested if‑statements, which quickly becomes unreadable and hard to maintain as the number of levels grows.
public class FirstPassHandler {
public int handler(){
System.out.println("第一关-->FirstPassHandler");
return 80;
}
}
public class SecondPassHandler {
public int handler(){
System.out.println("第二关-->SecondPassHandler");
return 90;
}
}
public class ThirdPassHandler {
public int handler(){
System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
return 95;
}
}
public class HandlerClient {
public static void main(String[] args){
FirstPassHandler first = new FirstPassHandler();
SecondPassHandler second = new SecondPassHandler();
ThirdPassHandler third = new ThirdPassHandler();
int firstScore = first.handler();
if(firstScore >= 80){
int secondScore = second.handler();
if(secondScore >= 90){
third.handler();
}
}
}
}When the number of levels increases to 100, the code becomes a deep cascade of if‑statements, making maintenance risky.
Initial Refactor
By linking each level as a node in a chain, the client no longer needs multiple if‑checks. Each handler holds a reference to the next handler and forwards the request when appropriate.
public class FirstPassHandler {
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler h){ this.secondPassHandler = h; }
private int play(){ return 80; }
public int handler(){
System.out.println("第一关-->FirstPassHandler");
if(play() >= 80 && secondPassHandler != null){
return secondPassHandler.handler();
}
return 80;
}
}
// Similar implementations for SecondPassHandler and ThirdPassHandler
public class HandlerClient {
public static void main(String[] args){
FirstPassHandler first = new FirstPassHandler();
SecondPassHandler second = new SecondPassHandler();
ThirdPassHandler third = new ThirdPassHandler();
first.setSecondPassHandler(second);
second.setThirdPassHandler(third);
first.handler();
}
}Drawbacks of the Initial Refactor
Each concrete handler must declare a different type for its next handler, making the chain hard to extend.
The code’s extensibility is poor.
Improved Chain Refactor
Introduce an abstract handler that defines a generic next reference, allowing all concrete handlers to share the same base type.
public abstract class AbstractHandler {
protected AbstractHandler next;
public void setNext(AbstractHandler next){ this.next = next; }
public abstract int handler();
}
public class FirstPassHandler extends AbstractHandler {
private int play(){ return 80; }
@Override
public int handler(){
System.out.println("第一关-->FirstPassHandler");
int score = play();
if(score >= 80 && next != null){
return next.handler();
}
return score;
}
}
// SecondPassHandler and ThirdPassHandler extend AbstractHandler similarly
public class HandlerClient {
public static void main(String[] args){
FirstPassHandler first = new FirstPassHandler();
SecondPassHandler second = new SecondPassHandler();
ThirdPassHandler third = new ThirdPassHandler();
first.setNext(second);
second.setNext(third);
first.handler();
}
}Factory‑Based Dynamic Chain Construction
Use an enum to describe each handler’s configuration (id, name, class name, previous and next ids). A factory reads the enum, creates handler instances via reflection, and links them automatically.
public enum GatewayEnum {
API_HANDLER(new GatewayEntity(1,"api接口限流","cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler",null,2)),
BLACKLIST_HANDLER(new GatewayEntity(2,"黑名单拦截","cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler",1,3)),
SESSION_HANDLER(new GatewayEntity(3,"用户会话拦截","cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler",2,null));
private GatewayEntity gatewayEntity;
GatewayEnum(GatewayEntity e){ this.gatewayEntity = e; }
public GatewayEntity getGatewayEntity(){ return gatewayEntity; }
}
// GatewayDao, GatewayImpl, GatewayHandlerEnumFactory, and GetewayClient classes follow the same logic as described in the source.Conclusion
The Chain of Responsibility is one of many design patterns; mastering it helps write cleaner, more maintainable code. Design patterns are an art that requires continuous learning and practice.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
