业务代码插件式开发实践

news2024/11/26 13:24:35

在学习编程初期,会接触到设计模式的概念: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,会造成一个巨大的类,内部字段何时填充,何时使用,使用目的,使用方都会变得不清晰。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【面试干货】Object 类中的公共方法详解

【面试干货】Object 类中的公共方法详解 1、clone() 方法2、equals(Object obj) 方法3、hashCode() 方法4、getClass() 方法5、wait() 方法6、notify() 和 notifyAll() 方法 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在 Java 中&#…

高频面试题基本总结回顾1(含笔试高频算法整理)

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

如何使用飞书快捷指令无感记账,ios版

总结 很多人无法长期坚持记账&#xff0c;主要是每次消费需要打开手机软件&#xff0c;一系列繁琐的操作&#xff0c;导致过程中可能就忘了。 今天给大家带来飞书自动记账。 演示视频 点击查看&#xff1a;https://www.douyin.com/video/7312857946382241063 安装 下载快捷…

【机器学习300问】135、决策树算法ID3的局限性在哪儿?C4.5算法做出了怎样的改进?

ID3算法是一种用于创建决策树的机器学习算法&#xff0c;该算法基于信息论中的信息增益概念来选择最优属性进行划分。信息增益是原始数据集熵与划分后数据集熵的差值&#xff0c;熵越小表示数据集的纯度越高。有关ID3算法的详细步骤和算法公式在我之前的文章中谈到&#xff0c;…

临时文件上传系统Plik

什么是 Plik &#xff1f; Plik 是一个基于 Go 语言的可扩展且用户友好的临时文件上传系统&#xff08;类似于 Wetransfer&#xff09;。 软件主要特点&#xff1a; 强大的命令行客户端易于使用的 Web 用户界面多个数据后端&#xff1a;文件、OpenStack Swift、S3、Google Clo…

单调队列优化DP——AcWing 135. 最大子序和

单调队列优化DP 定义 单调队列优化DP是一种在动态规划&#xff08;Dynamic Programming, DP&#xff09;中应用的数据结构优化方法。它利用单调队列&#xff08;Monotonic Queue&#xff09;这一数据结构来高效维护一个区间内的最值&#xff08;通常是最大值或最小值&#xf…

特斯拉下一代自动驾驶芯片的深度预测

引言 特斯拉一直以来都在自动驾驶技术上不断突破&#xff0c;随着AI大模型技术的爆发&#xff0c;其下一代自动驾驶芯片&#xff08;HW5.0&#xff09;也备受瞩目。本文将深入分析和预测特斯拉下一代自动驾驶芯片AI5的技术特点及其对行业的影响。 深入技术分析 现有自动驾驶…

【电机控制】EG2134无刷电机驱动板——最小核心板STM32F103C8T6,开环、无感SMO验证

【电机控制】EG2134无刷电机驱动板——最小核心板STM32F103C8T6&#xff0c;开环、无感SMO验证 文章目录 前言一、硬件二、软件三、开环SVPWM四、SMO无感观测器五、参考文献总结 前言 【电机控制】直流有刷电机、无刷电机汇总——持续更新 使用工具&#xff1a; 1.控制器&…

大创项目推荐 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python

文章目录 1 简介2 传统机器视觉的手势检测2.1 轮廓检测法2.2 算法结果2.3 整体代码实现2.3.1 算法流程 3 深度学习方法做手势识别3.1 经典的卷积神经网络3.2 YOLO系列3.3 SSD3.4 实现步骤3.4.1 数据集3.4.2 图像预处理3.4.3 构建卷积神经网络结构3.4.4 实验训练过程及结果 3.5 …

linux 文件管理

一、linux文件命名规则 1.可使用字符&#xff1a;所有字符都可使用&#xff0c;不建议使用"<、>、&#xff1f;、*"等特殊字符 正常文件命名规则&#xff1a; 1.尽量使用小写 2.在需要对文件名分割时&#xff0c;建议使用“_”&#xff0c;chen_2004_06_28…

【高中数学之基本不等式】已知:a,b皆为正实数且1/a+1/(b+2)=1/2 求:a+b的最小值?

解&#xff1a;先从1/a1/(b2)1/2 入手&#xff0c;看能否化二为一&#xff08;将两变量化成一个变量&#xff09; 由1/a1/(b2)1/2 两边通分得(b2a)/a/(b2)1/2 交叉相乘得2a2b4ab2a 最后得到a24/b 所以ab24/bb 此时已经可以用基本不等式了 ab24/bb>22*根号下(4/b*b)22…

CSS 核心知识点 - grid

思维导图 参考网址: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_grid_layout 一、什么是 grid&#xff1f; CSS Grid布局是在CSS3规范中引入的一种新的布局方式&#xff0c;旨在解决传统布局方法&#xff08;如浮动、定位、表格布局&#xff09;存在的许多问题。C…

运行CDN

背景 CDN代码&#xff0c;调试运行 日常 git clone代码配置虚拟环境 puthon3.8,pip install r requirements.txt改项目数据集路径&#xff0c;在hico.py文件里面 # PATHS {# train: (root / images / train2015, root / annotations / trainval_hico.json),# val: …

【Python】已解决:ModuleNotFoundError: No module named ‘nltk‘

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;ModuleNotFoundError: No module named ‘nltk‘ 一、分析问题背景 在Python编程中&#xff0c;我们常常需要使用第三方库来扩展语言的功能和应用场景。NLTK&am…

DataWhale-吃瓜教程学习笔记(四)

学习视频&#xff1a;第3章-二分类线性判别分析_哔哩哔哩_bilibili 西瓜书对应章节&#xff1a; 3.4 文章目录 - 算法原理- 损失函数推导-- 异类样本中心尽可能远-- 同类样本方差尽可能小-- 综合 知识点补充 - 二范数二范数&#xff08;2-norm&#xff09;详解定义几何意义性质…

Bridging nonnull in Objective-C to Swift: Is It Safe?

Bridging nonnull in Objective-C to Swift: Is It Safe? In the world of iOS development, bridging between Objective-C and Swift is a common practice, especially for legacy codebases (遗留代码库) or when integrating (集成) third-party libraries. One importa…

链表-求链表中环的入口结点(easy)

目录 一、问题描述 二、解题思路 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 本题基本思路&#xff1a; 1.设置一个hashSet来存储已经访问过的链表结点地址&#xff0c;注意不要直接存储链表内元素&#xff0c;因为链表内元素可能存在重复的&#xff0c;地址是不…

APP项目测试 之 开发模型和发布

项目客户端一般分为&#xff1a;浏览器端和APP端 APP端分为&#xff1a;手机端&#xff08;安装在手机上的软件&#xff09;和PC端&#xff08;安装在电脑上的软件&#xff09; 1.开发模型 项目迭代速度不同&#xff1a;开发模型不一样 传统行业&#xff1a;瀑布模型 互联网行业…

C++实现简化版Qt信号槽机制(2):增加内存安全保障

在上一篇文章中《C实现一个简单的Qt信号槽机制》&#xff0c;我们基于前面的反射代码实现了信号槽的功能。 但是上一篇的代码中没有对象生命周期管理的机制&#xff0c;如果在对象的生命周期结束后还存在未断开的信号和槽连接&#xff0c;那么信号触发时可能会尝试访问已经被析…

乐鑫 Matter 技术体验日|快速落地 Matter 产品,引领智能家居生态新发展

随着 Matter 协议的推广和普及&#xff0c;智能家居行业正迎来新的发展机遇&#xff0c;众多厂商纷纷投身于 Matter 产品的研发与验证。然而&#xff0c;开发者普遍面临技术门槛高、认证流程繁琐、生产管理复杂等诸多挑战。 乐鑫信息科技 (688018.SH) 凭借深厚的研发实力与行…