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