Fundamentals 11 min read

Understanding the Chain of Responsibility Design Pattern with Java Examples

This article explains the Chain of Responsibility design pattern, its typical use cases, shows problematic implementations, and demonstrates step‑by‑step refactorings—including abstract handlers, client simplification, and enum‑based factory configuration—using clear Java code examples.

Architect's Guide
Architect's Guide
Architect's Guide
Understanding the Chain of Responsibility Design Pattern with Java Examples

Background : The author describes a failed attempt to implement an import feature using the Chain of Responsibility pattern, resulting in overly complex and buggy code.

What is Chain of Responsibility : It is a behavioral design pattern that passes a request along a chain of handlers, each of which may process the request or forward it.

Usage scenarios : multi‑condition workflow, ERP approval processes, Java servlet filters, and similar situations where request handling can be delegated.

Anti‑example : A game with three levels is implemented as separate handler classes, and the client contains nested if statements, leading to redundant and hard‑to‑maintain code.

Initial refactor : Shows linking handlers via member variables and setter methods to reduce client logic.

Improved refactor : Introduces an abstract handler with a next reference, concrete handlers overriding handler(), and client code that only sets the chain.

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 have similar structure, returning 90 and 95 respectively.

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();
    }
}

Drawbacks of the original approach : each level stores a different next reference, the code is not extensible, and modifying the chain risks large, error‑prone changes.

Further refactor – factory and enum configuration : Handlers are described in an enum (GatewayEnum) with IDs and fully‑qualified class names, loaded via reflection, and linked dynamically to form the chain.

public enum GatewayEnum {
    API_HANDLER(new GatewayEntity(1, "api limit", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
    BLACKLIST_HANDLER(new GatewayEntity(2, "blacklist", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
    SESSION_HANDLER(new GatewayEntity(3, "session", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null));
    private GatewayEntity gatewayEntity;
    GatewayEnum(GatewayEntity gatewayEntity) { this.gatewayEntity = gatewayEntity; }
    public GatewayEntity getGatewayEntity() { return gatewayEntity; }
}

public class GatewayHandlerEnumFactory {
    private static GatewayDao gatewayDao = new GatewayImpl();
    public static GatewayHandler getFirstGatewayHandler() {
        GatewayEntity first = gatewayDao.getFirstGatewayEntity();
        GatewayHandler firstHandler = newGatewayHandler(first);
        GatewayEntity currentEntity = first;
        GatewayHandler currentHandler = firstHandler;
        Integer nextId;
        while ((nextId = currentEntity.getNextHandlerId()) != null) {
            GatewayEntity nextEntity = gatewayDao.getGatewayEntity(nextId);
            GatewayHandler nextHandler = newGatewayHandler(nextEntity);
            currentHandler.setNext(nextHandler);
            currentHandler = nextHandler;
            currentEntity = 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 pattern is a powerful tool for building flexible processing pipelines, but it should be applied judiciously; overusing it can lead to unnecessary complexity.

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 ResponsibilityJavarefactoring
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.