Fundamentals 9 min read

How to Use the Chain of Responsibility Pattern for Real‑World Data Filtering

This article explains the Chain of Responsibility design pattern, defines its three core roles, compares external and internal composition, demonstrates a lead‑filtering use case with Java and Spring code, and discusses benefits, drawbacks, and differences from the Strategy pattern.

IT Niuke
IT Niuke
IT Niuke
How to Use the Chain of Responsibility Pattern for Real‑World Data Filtering

The Chain of Responsibility pattern models a sequence of processing steps where each step (handler) can decide whether to handle the request and whether to pass it on. Typical examples include approval workflows and multi‑stage interviews.

Core Roles

Client : assembles the handlers, similar to a singly linked list; can be built statically or at runtime.

Handler : an interface or abstract class that defines the processing method and its parameters.

ConcreteHandler : a concrete implementation that performs a specific check or transformation.

While the pattern offers flexibility, it can make the flow less obvious and may introduce circular references that lead to infinite loops.

Composition Styles

External composition : each concrete handler works only on the Context object and writes results back; the ordering is managed outside the handlers, yielding highly independent processing units.

Internal composition : handlers are linked like a chain and decide at runtime whether to invoke the next handler, which reduces flexibility slightly.

Business Example: Lead Filtering

In a lead‑capture pipeline, a LeadsBO object must satisfy several rules before being stored in a CRM system.

@Data
@Builder
public class LeadsBO {
    // channel ID
    private String channel;
    // referrer ID
    private Long referrerId;
    // subject: 1=Chinese, 2=English
    private Integer subject;
    // landing page version
    private String lpv;
    // preferred language
    private String preferenceLanguage;
    // result fields
    @Builder.Default
    private boolean valid = true;
    private String errMsg;
}

The Filter interface defines a single process(LeadsBO bo) method.

public interface Filter {
    void process(LeadsBO bo);
}

Four concrete filters implement the business rules:

@Component("channel")
public class ChannelFilter implements Filter {
    @Override
    public void process(LeadsBO bo) {
        if (StringUtils.isBlank(bo.getChannel())) {
            bo.setValid(false);
            bo.setErrMsg("channel为空");
        }
    }
}

@Component("referrer")
public class ReferrerFilter implements Filter {
    private static final Set<String> CHANNELS = Set.of("LP-XX-XX-01", "LP-XX-XX-02");
    @Override
    public void process(LeadsBO bo) {
        String channel = bo.getChannel();
        Long referrerId = bo.getReferrerId();
        if (CHANNELS.contains(channel) && Objects.isNull(referrerId)) {
            bo.setValid(false);
            bo.setErrMsg("特定渠道号, 推荐人不能为空");
        }
    }
}

@Component("lpv")
public class LpvFilter implements Filter {
    @Override
    public void process(LeadsBO bo) {
        String lpv = bo.getLpv();
        if (StringUtils.isNoneBlank(lpv) && lpv.startsWith("XZ") && StringUtils.isBlank(bo.getPreferenceLanguage())) {
            bo.setValid(false);
            bo.setErrMsg("偏好语言不能为空");
        }
    }
}

@Component("subject")
public class SubjectFilter implements Filter {
    @Override
    public void process(LeadsBO bo) {
        Integer subject = bo.getSubject();
        if (Objects.isNull(subject) || (subject != 1 && subject != 2)) {
            bo.setSubject(1);
        }
    }
}

The filtering chain is executed by LeadsFilterService, which obtains handler beans by name from the Spring ApplicationContext (via ApplicationContextAware) and runs them in the order supplied by the caller.

public interface LeadsFilterService {
    void filter(LeadsBO bo, List<String> filterChain);
}

@Slf4j
@Service
public class LeadsFilterServiceImpl implements LeadsFilterService, ApplicationContextAware {
    private ApplicationContext appCtx;
    @Override
    public void filter(LeadsBO bo, List<String> filterChain) {
        for (String fc : filterChain) {
            Filter filter = appCtx.getBean(fc, Filter.class);
            filter.process(bo);
            if (!bo.isValid()) {
                log.error("[线索拦截] 数据异常 errMsg=>{}", bo.getErrMsg());
                return;
            }
        }
    }
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.appCtx = ctx;
    }
}

Unit tests verify the chain behavior:

@SpringBootTest
class LeadsFilterServiceTest {
    @Autowired
    LeadsFilterService service;

    @Test
    void testChannel() {
        LeadsBO bo = LeadsBO.builder().build();
        service.filter(bo, List.of("channel", "subject", "lpv", "referrer"));
        assertFalse(bo.isValid());
    }

    @Test
    void testReferrer() {
        LeadsBO bo = LeadsBO.builder().channel("LP-XX-XX-01").referrerId(123L).build();
        service.filter(bo, List.of("channel", "subject", "lpv", "referrer"));
        assertEquals(1, bo.getSubject());
        assertTrue(bo.isValid());
    }
}

What the Pattern Brings

High cohesion and low coupling: each business rule lives in its own handler.

Easy extensibility: adding a new rule only requires creating a new handler implementation.

Reusability: the same handlers can be assembled in different orders for different scenarios.

Difference from Strategy Pattern

Strategy selects a single algorithm to execute; other strategies are not involved.

Chain of Responsibility allows multiple handlers to act sequentially, emphasizing the chain behavior.

Practical Tips

Avoid overusing the pattern; apply it only when a clear ordered processing pipeline is needed.

Ensure the chain does not contain circular references that could cause infinite loops.

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 PatternsJavaSpringUnit TestingDependency Injection
IT Niuke
Written by

IT Niuke

Focused on IT technology sharing, original and innovative content. IT Niuke, we grow 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.