Fundamentals 13 min read

Understanding the Chain of Responsibility Design Pattern with Java Examples

This article explains the Chain of Responsibility design pattern, its definition, typical use cases, drawbacks of a naïve implementation, and step‑by‑step refactoring using abstract handlers, a handler factory, and Java code samples to illustrate a clean, extensible solution.

Top Architect
Top Architect
Top Architect
Understanding the Chain of Responsibility Design Pattern with Java Examples

Background – The author describes a situation 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 the Template Method would have been more appropriate.

What is the Chain of Responsibility? – It is a behavioral design pattern that lets a request travel along a chain of handlers, each of which can either process the request or pass it to the next handler.

Typical Usage Scenarios

Multi‑condition flow control such as permission checks

ERP workflow approvals (e.g., general manager, HR manager, project manager)

Underlying implementation of Java servlet filters

Anti‑Pattern Example – A simple game with three levels is implemented using nested if statements, leading to duplicated code and poor maintainability. The full source code is shown below:

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 firstPassHandler = new FirstPassHandler();
        SecondPassHandler secondPassHandler = new SecondPassHandler();
        ThirdPassHandler thirdPassHandler = new ThirdPassHandler();
        int firstScore = firstPassHandler.handler();
        if (firstScore >= 80) {
            int secondScore = secondPassHandler.handler();
            if (secondScore >= 90) {
                thirdPassHandler.handler();
            }
        }
    }
}

Extending this approach to 100 levels would produce an even more unreadable chain of nested if statements.

Initial Refactor – By linking each level as a node in a chain, the client no longer needs multiple if checks. The refactored handlers hold a reference to the next handler and delegate when the current condition is satisfied.

public class FirstPassHandler {
    private SecondPassHandler secondPassHandler;
    public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
        this.secondPassHandler = secondPassHandler;
    }
    private int play() { return 80; }
    public int handler() {
        System.out.println("第一关-->FirstPassHandler");
        if (play() >= 80 && this.secondPassHandler != null) {
            return this.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 Refactor

Each concrete handler must declare a specific field for its next handler, making the chain hard to modify.

The code’s extensibility remains limited.

Further Refactor with an Abstract Handler – Introduce an abstract base class that defines a generic next reference and an abstract handler() method. Concrete handlers extend this class, reducing coupling.

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");
        if (play() >= 80 && next != null) {
            return next.handler();
        }
        return play();
    }
}
// SecondPassHandler and ThirdPassHandler follow the same pattern
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 Chain Construction – The chain can be configured via an enum and a factory that builds handlers reflectively based on configuration data, allowing dynamic assembly without hard‑coding class dependencies.

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));
    // ... getters and constructor omitted for brevity
}

public class GatewayHandlerEnumFactory {
    private static GatewayDao gatewayDao = new GatewayImpl();
    public static GatewayHandler getFirstGatewayHandler() {
        GatewayEntity first = gatewayDao.getFirstGatewayEntity();
        GatewayHandler firstHandler = newGatewayHandler(first);
        if (firstHandler == null) return null;
        GatewayEntity cur = first;
        GatewayHandler curHandler = firstHandler;
        Integer nextId;
        while ((nextId = cur.getNextHandlerId()) != null) {
            GatewayEntity nextEntity = gatewayDao.getGatewayEntity(nextId);
            GatewayHandler nextHandler = newGatewayHandler(nextEntity);
            curHandler.setNext(nextHandler);
            curHandler = nextHandler;
            cur = nextEntity;
        }
        return firstHandler;
    }
    private static GatewayHandler newGatewayHandler(GatewayEntity entity) {
        try {
            Class<?> clazz = Class.forName(entity.getConference());
            return (GatewayHandler) clazz.newInstance();
        } catch (Exception e) { e.printStackTrace(); }
        return null;
    }
}

Conclusion – The Chain of Responsibility is one of many design patterns; it helps decouple request senders from processing logic, improves maintainability, and can be further refined with abstract handlers and factory‑based configuration.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Chain of ResponsibilityDesign PatternsJavarefactoring
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.