业务代码插件式开发实践

在学习编程初期,会接触到设计模式的概念:23种设计模式,单例模式,策略模式,… 。接触业务研发后,对设计模式的使用和实践有了更深的见解。

使用设计模式是目的为了更高效的支撑业务诉求,如何在保证代码质量的情况下变更/扩展现有功能,这里的代码质量可分为健壮性和可读性两大类,分别包含以下方面:

  • 健壮性:并发控制、事务、单点变更、性能、可测试
  • 可读性:业务语义、圈复杂度、认知复杂度、coding style

往往在变更/扩展一块功能时,最耗时费力的不是功能编码,而是待开发功能对已有功能影响的梳理。代码质量的好坏对该过程起着决定性作用,进一步与系统Bug率也有着强关联。

但在接触到具体业务代码时,我们往往以“屎山”代称,这里有两类原因:

  1. 业务处于扩张期,业务形态未完全定型,业务需求量大且急,此时代码质量并不是首要考虑因素,而是要快速支持业务。

提升代码质量不是目的,而是手段。最终目的都是为了支撑业务。

  1. 研发人员没有意识,研发团队没有规范且无强卡点

不设置强卡点的规范都不是规范

本文讨论了一些业务研发过程中常用的插件式开发形式,以及衍生出常用的两种设计模式:策略模式和责任链模式。

插件式开发

在说明设计模式之前,先了解以下插件式开发,插件式开发对于已有功能提供了一种扩展可能,同时在变更已有功能时不会因为逻辑耦合导致影响其他功能。

Spring官方提供了一种插件组件:Spring Plugin

在业务开发中结合Spring也可以完成可插拔的设计,我将它成为supports模式。

比如这里的用户触达场景,用户可以通过短信、邮件、站内信方式接受通知,不同的途径会生成不同的消息体。因此我们有:

// 通知方式
enum MessageType{ SMS,EMAIL,APP }

// 用于生成消息的输入上下文
class MessageContext {...}

在设计消息生成接口时,我们添加一个supports方法表示该实现类支持的生成方式:

public interface MessageGenerate {

    // 生成消息字符串方法
    String generate(MessageContext context);
    
    // 支持的消息类型
    boolean supports(MessageType messageType);
}

这样,支持生成何种类型消息的控制权就由实现类自行决定,比如我需要实现站内信的支持:

class APPMessageGenerate implements MessageGenerate {
    @Override
    public String generate(MessageContext context) {
        // ...
        return "";
    }

    @Override
    public boolean supports(MessageType messageType) {
        return Objects.equals(messageType, MessageType.APP);
    }
}

再比如,这里的短信和邮件使用一种格式,那我们只需要一个实现类即可:

class SMSEmailMessageGenerate implements MessageGenerate {
    @Override
    public String generate(MessageContext context) {
        // ...
        return "";
    }

    @Override
    public boolean supports(MessageType messageType) {
        return Objects.equals(messageType, MessageType.SMS)
            || Objects.equals(messageType, MessageType.EMAIL);
    }
}

这样的设计在Spring Security中尤为常见,可以参看:Spring Security 实践

策略模式

在此基础上,构建一个门面可以实现策略模式:

@Component
class MessageFacade{
    
    @Autowired
    private List<MessageGenerate> generators;
    
    // 根据MessageType生成对应的消息
    public String generateByMessageType(MessageType messageType, MessageContext context) throws OperationNotSupportedException {
        return generators.stream()
                   .filter(message -> message.supports(messageType))
                   .findFirst()
                   .map(generator -> generator.generate(context))
                   .orElseThrow(OperationNotSupportedException::new);
    }
}

在使用时直接调用MessageFacade#generateByMessageType即可,后续扩展,添加实现类即可,Spring可自动注入MessageGenerate接口的所有实现类。

责任链模式

另外一个case,这里我想把所有消息类型的消息都拼接在一起,并且指定拼接顺序,我们可以构建一个责任链,不同的类型按顺序生成不同的消息,此时我们需要依赖顺序,
MessageGenerate可以继承Spring的org.springframework.core.Ordered 接口:

// 继承自org.springframework.core.Ordered接口
public interface MessageGenerate extends Ordered {
    ...
}
// 实现类实现getOrder方法
class APPMessageGenerate implements MessageGenerate {
    
    ...
    
    // 值越小顺序越靠前
    @Override
    public int getOrder() {
        return 0;
    }
}

或不需要继承Ordered接口,在实现类注解 Spring的org.springframework.core.annotation.Order / java的javax.annotation.Priority

// 实现类注解org.springframework.core.annotation.Order,值越小顺序越靠前
@Order(-100)
class APPMessageGenerate implements MessageGenerate {
    
    ...
}

在门面中构建顺序责任链:

@Component
class MessageFacade{

    // 责任链
    @Autowired
    private List<MessageGenerate> generators;
    
    // 排序
    @PostConstruct
    private void init() {
        AnnotationAwareOrderComparator.sort(generators);
    }
    
    public String generateAndConcatMessage(MessageContext context){
        StringBuilder sb = new StringBuilder();
        generators.forEach(generator -> sb.append(generator.generate(context)));
        return sb.toString();
    }
    
}

进一步,如果需要有复杂且可复用的support逻辑,可以单独抽象出一个匹配器接口:

interface MessageMatcher {
    boolean matches(MessageContext context);
}
// 这里以类型匹配为例
@RequiredArgsConstructor
class MessageTypeMatcher implements MessageMatcher {
    
    private final MessageType targetMessageType;

    @Override
    public boolean matches(MessageContext context) {
        return Objects.equals(targetMessageType, context.getMessageType());
    }
}

这样实现类可以将匹配逻辑委托给MessageMatcher,实现逻辑解耦:

@Order(-100)
class APPMessageGenerate implements MessageGenerate {

    private final MessageMatcher messageMatcher = new MessageTypeMatcher(MessageType.APP);

    @Override
    public String generate(MessageContext context) {
        // ...
        return "";
    }

    // 这里我们将support的输入扩大为整个context
    @Override
    public boolean supports(MessageContext messageType) {
        return messageMatcher.matches(messageType);
    }

}

整个UML图如下:

在这里插入图片描述

但是如果我们要在执行过程中控制是否继续这个责任链呢?这里Spring Security给出的解决方式是,在接口下构造一个抽象类记录责任链执行进度,实现类每次执行完都要调用super().generate(...)方法表示继续这个责任链,否则不继续。这样要求generate方法返回值必须为void,因此,将输出结果作为责任链入参传入,在执行过程中填充,其接口变为:

interface MessageGenerate {

    // 生成消息字符串方法
    void generate(MessageInputContext inputContext, MessageOutputContext outputContext);

    // 支持的消息类型
    boolean supports(MessageInputContext context);
}

class MessageOutputContext {
    List<String> messages;
}

进一步,我们可以利用void返回值,让其返回boolean值表示是否继续责任链:

interface MessageGenerate {
    // boolean表示是否继续责任链
    boolean generate(MessageInputContext inputContext, MessageOutputContext outputContext);

    boolean supports(MessageInputContext context);
}

这样最终的Facade中的逻辑为:

@Component
class MessageFacade{

    @Autowired
    private List<MessageGenerate> generators;

    @PostConstruct
    private void init() {
        AnnotationAwareOrderComparator.sort(generators);
    }

    public MessageOutputContext generateMessages(MessageInputContext inputContext){
        MessageOutputContext outputContext = new MessageOutputContext();
        for (MessageGenerate generator : generators) {
            // 返回为false表示不继续,直接break
            if (!generator.generate(inputContext, outputContext)) {
                break;
            }
        }
        return outputContext;
    }
}

弊端

可以看到这里所有实现类共用了一个Context,会造成一个巨大的类,内部字段何时填充,何时使用,使用目的,使用方都会变得不清晰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/755409.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

selenium4如何指定chrome和firefox的驱动(driver)路径

pythonpytestselenium框架的自动化测试脚本。 原本用的chrome&#xff0c;很久没用了&#xff0c;今天执行&#xff0c;发现chrome偷偷升级&#xff0c;我的chromedriver版本不对了。。。鉴于访问chrome相关网站太艰难&#xff0c;决定弃用chrome&#xff0c;改用firefox。因为…

基于SSM+Jsp的疫情居家办公OA系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

vue使用glide.js实现轮播图(可直接复制使用)

效果图 可以实现自动轮播&#xff0c;3种切换方式&#xff1a;直接滑动图片、点击两侧按钮、点击底部按钮 体验链接:http://website.livequeen.top 实现 一、引入依赖 1、控制台引入依赖 npm install glidejs/glide 2、在css中引用 <style scoped> import glidejs/g…

华为达芬奇与英伟达CUDA架构对比分析

华为达芬奇与英伟达CUDA&#xff0c;必有一战&#xff01; 大数据产业创新服务媒体 ——聚焦数据 改变商业 当初英特尔和微软&#xff0c;搞出来个Wintel&#xff0c;制霸电脑时代很多年。从某种意义上&#xff0c;英伟达的CUDA&#xff0c;就相当于CPU时代的windows&#x…

高性价比 ESP32 网络收音机:OLED 显示+编码器控制 (源码开源)

摘要: 本文将详细介绍如何使用 ESP32 开发板制作一个功能完备的网络收音机。我们将涵盖硬件选择、软件架构、网络连接、音频流解码、用户界面设计等方面&#xff0c;并提供完整的代码示例和详细的解释&#xff0c;帮助您轻松构建自己的网络收音机。 关键词: ESP32, 网络收音机…

Python | Leetcode Python题解之第204题计数质数

题目&#xff1a; 题解&#xff1a; MX5000000 is_prime [1] * MX is_prime[0]is_prime[1]0 for i in range(2, MX):if is_prime[i]:for j in range(i * i, MX, i):#循环每次增加iis_prime[j] 0 class Solution:def countPrimes(self, n: int) -> int:return sum(is_prim…

前端通过ResizeObserver来监听dom大小动态渲染echarts

export const GlobalResizeObserver (function () {const ATTR_NAME global-resizeobserver-keyconst attrValueToCallback {}function antiShake(fn, delay, immediate false) {let timer null//不能用箭头函数return function () {//在时间内重复调用的时候需要清空之前…

Linux Vim最全面的教程

Linux Vim简介 Linux Vim 是一个高度可定制的文本编辑器&#xff0c;广泛用于 Linux 和类 Unix 系统中。它起源于 Vi&#xff0c;一个早期的 Unix 系统中的编辑器&#xff0c;Vim 是 "Vi IMproved"&#xff08;改进版 Vi&#xff09;的缩写。Vim 继承了 Vi 的许多特性…

理解论文笔记:基于贝叶斯网络和最大期望算法的可维护性研究

看了与上一篇研究方向一致的文章&#xff0c;上一篇19年的&#xff0c;这一篇22年的更新。若有侵权&#xff0c;请联系删除。 I. INTRODUCTION 介绍 主要介绍了使用贝叶斯网络和历史数据对无线传感器网络可维护性研究的重要性和必要性&#xff0c;并对下面的各章进行了…

为什么有的手机卡没有语音功能呢?

大家好&#xff0c;今天这篇文章为大家介绍一下&#xff0c;无通话功能的手机卡&#xff0c; 在网上申请过手机卡的朋友应该都知道&#xff0c;现在有这么一种手机卡&#xff0c;虽然是运营商推出的正规号卡&#xff0c;但是却屏蔽了通话功能&#xff0c;你知道这是为什么吗&am…

APP项目测试 之 APP功能测试

1. APP测试流程 需求评审——计划编写——用例设计——用例执行——缺陷管理——测试报告 2. APP测试内容 功能测试 专项测试 性能测试 3.注册测试点扩充 4.登录测试点扩充 5.购物车测试点扩充 6.搜索测试点扩充 7.支付测试点扩充 8.评论测试点扩充 未完待续。…

decode()方法——解码字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 解码是将字节流转换成字符串&#xff08;文本&#xff09;&#xff0c;其他编码格式转成unicode。在Python中提供了decode()方法&#xff0…

layui在表格中嵌入上传按钮,并修改上传进度条

当需要在表格中添加上传文件按钮&#xff0c;并不需要弹出填写表单的框的时候&#xff0c;需要在layui中&#xff0c;用按钮触发文件选择 有一点需要说明的是&#xff0c;layui定义table并不是在定义的标签中渲染&#xff0c;而是在紧接着的标签中渲染&#xff0c;所以要获取实…

小模型家族又新增成员Gemma2

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

java大型医院绩效考核系统源码(医院为什么需要绩效机制?)医院绩效考核系统源码 医院管理绩效考核系统源码

java大型医院绩效考核系统源码&#xff08;医院为什么需要绩效机制&#xff1f;&#xff09;医院绩效考核系统源码 医院管理绩效考核系统源码 医院作为提供医疗服务的核心机构&#xff0c;其运营和管理效率直接影响到患者的就医体验、治疗效果以及医院的长期发展。因此&#xf…

Java编写学籍信息管理系统,完整代码

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

基于香农编码的图像压缩算法实现,聊聊!

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

【语言模型】探索AI模型、AI大模型、大模型、大语言模型与大数据模型的关系与协同

一、引言 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;各种AI模型如雨后春笋般涌现&#xff0c;其中AI模型、AI大模型、大模型、大语言模型以及大数据模型等概念在学术界和工业界引起了广泛关注。这些模型不仅各自具有独特的特点和应用场景&#xff0c;…

我关于Excel使用点滴的笔记

本篇笔记是我关于Excel使用点滴的学习笔记&#xff0c;摘要和地址链接列表。临时暂挂&#xff0c;后面可能在不需要时删除。 (笔记模板由python脚本于2024年06月28日 12:23:32创建&#xff0c;本篇笔记适合初通Python&#xff0c;熟悉六大基本数据(str字符串、int整型、float浮…

有人问周鸿祎: 学历不重要,为什么360只要985和211?

关注、星标公众号&#xff0c;直达精彩内容 有人问周鸿祎:你说学历不重要&#xff0c;为什么360招聘的人才只要985和211&#xff1f;他说这个事情&#xff0c;我专门问了我们的人力资源&#xff0c;我们的干品分为校园招聘和社会招聘 校园招聘的话会看文凭 社会招聘的话&#x…