个别做业务开发,不太容易有大量应用设计模式的场景。这里总结一下在业务开发中应用较为频繁的设计模式。当然语言为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/desi...