写点什么

工作中常用的设计模式 -- 责任链模式

作者:lpe234
  • 2022-12-07
    北京
  • 本文字数:2930 字

    阅读完需:约 10 分钟

工作中常用的设计模式--责任链模式

一般做业务开发,不太容易有大量使用设计模式的场景。这里总结一下在业务开发中使用较为频繁的设计模式。当然语言为 Java,基于 Spring 框架。

1 责任链模式(Chain of Responsibility Pattern)

对数据流、对象做一系列过滤、校验、处理等操作,该操作通常是有序的。该场景在生活中还是很常见的,如大家经常举例的各种审批流。再如面试,需要通过一面、二面、三面、HR 面等,最终拿下 Offer。这些都属于责任链的场景,


责任链模式一般有 3 个角色:


  • Client: 客户端。用来编排处理器,可静态、也可运行时动态生成。类似单链表。

  • Handler: 接口或抽象类。约定处理函数名称、参数等。

  • ConcreteHandler: 具体处理器。


这种出现的场景其实挺多,但真正使用的却不是很多。这就要提到责任链的几个缺点:灵活动态既是优点也是缺点,使得程序不够直观,流程不够明了。甚至可能出现循环引用(死循环)的情况。


责任链的编排一般有两种:


  • 外部编排:具体处理器,仅根据 Contex 参数进行处理,并将结果回写 Contex。内部不做编排。相对比较独立(纯粹)的逻辑处理单元。

  • 内部编排:类似单链表。在执行过程中,根据 Context 参数的处理结果,判断是否需要进行下一步(进入下一个处理器)。相对外部编排,灵活度略低一些。

1.1 实际业务场景

在用户留资进线场景中,我们需要根据线索的渠道号、落地页来源、学科等做不同的逻辑处理。但在未得到实际参数前,我们是不能确定具体要走哪些处理器。一些业务规则(简化):


  • 渠道号(channel)不能为空

  • 某些特殊渠道号,推荐人不能为空

  • 某些落地页来源(lpv),偏好语言不能为空


只有全部符合条件的线索,才能实际进入到 CRM 系统中。


线索 LeadsBO


@Data@Builderpublic class LeadsBO {    // 渠道号    private String channel;    // 推荐人ID    private Long referrerId;    // 学科 1:中文 2:英文    private Integer subject;    // 落地页版本(Landing Page Version)    private String lpv;    // 偏好语言    private String preferenceLanguage;
// 责任链回写 // 数据是否有效 @Builder.Default private boolean valid = true; // 异常信息(错误日志) private String errMsg;}
复制代码


线索过滤器(即 Handler 处理器) Filter


/** * 线索过滤器 */public interface Filter {
void process(LeadsBO bo);}
复制代码


4 个具体处理器



/** * 渠道号过滤器 */@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) { final String channel = bo.getChannel(); final 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) { final 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) { final Integer subject = bo.getSubject(); if (Objects.isNull(subject) || (subject != 1 && subject != 2)) { bo.setSubject(1); } }}
复制代码


可以看到,我们在定义处理器时,并没有在处理器内部动态指定下一个处理。我们将处理器的编排放在外部设置,这样更加的灵活一下。借助于 Spring,给具体处理添加别名,方便后续使用。


下面再定义一个线索拦截服务,其实就是将 Client 的部分逻辑进行封装,使得调用更加简单一些。只需要外部传递参数(LeadsBO),以及处理器别名的编排序列(List<String>)即可。


LeadsFilterService


/** * 线索拦截器 */public interface LeadsFilterService {
void filter(LeadsBO bo, List<String> filterChain);}
/** * 线索拦截器 */@Slf4j@Servicepublic class LeadsFilterServiceImpl implements LeadsFilterService, ApplicationContextAware { private ApplicationContext appCtx;
@Override public void filter(LeadsBO bo, List<String> filterChain) { for (String fc : filterChain) { final 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 { appCtx = ctx; }}
复制代码


借助ApplicationContextAware拿到 Spring 容器上下文,方便根据别名获取 Bean 对象。

1.2 单测

@SpringBootTestclass LeadsFilterServiceTest {
@Autowired LeadsFilterService service;
@Test void testChannel() { final LeadsBO bo = LeadsBO.builder().build(); service.filter(bo, List.of("channel", "subject", "lpv", "referrer")); assertFalse(bo.isValid()); }
@Test void testReferrer() { final 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()); }}
复制代码

2 思考

责任链模式能带来什么?


  1. 高内聚低耦合,将具体业务逻辑浓缩到具体处理器。

  2. 易于拓展,有新逻辑,直接继承、或实现处理器即可。

  3. 若有多种场景,需要不同的编排时,可高效复用这些处理器。


跟策略模式有啥区别?


  1. 策略模式一般只会由某个具体的策略去处理,其他策略不会参与。

  2. 责任链模式则是每个处理器都有可能获得处理权限。重点在于链式。


有啥注意事项呢?


  1. 避免滥用。但在合适的场景要大胆去使用。

  2. 一定要避免出现循环引用的情况。




封面图来源:https://refactoring.guru/design-patterns/chain-of-responsibility




echo '5Y6f5Yib5paH56ugOiDmjpjph5Eo5L2g5oCO5LmI5Zad5aW26Iy25ZWKWzkyMzI0NTQ5NzU1NTA4MF0pL+aAneWQpihscGUyMzQp' | base64 -d
复制代码


发布于: 刚刚阅读数: 5
用户头像

lpe234

关注

路漫漫其修远兮 2018-07-04 加入

还未添加个人简介

评论

发布
暂无评论
工作中常用的设计模式--责任链模式_Java_lpe234_InfoQ写作社区