实际案例进行代码设计演进:无状态的类

目录

      • 面向过程
      • 封装计算进`Task`
      • 封装计算进`Calculator`
      • 代码演进中做了什么
      • 学到了什么

在软件设计中,当选择把一个类设计为有状态后,往往意味着不安全、重量级,需要更多的资源来维护,而无状态在很多场景下是一个非常好的选择。

举个例子来探讨下。

我们业务逻辑中B端派发任务,C端接单完成任务,其中有个任务实体Task,需要根据业务金额taskMoney及服务费率serviceFeeRate收取B端服务费serviceFee,计算逻辑如下

                          taskMoney * serviceFeeRate = serviceFee

那么该如何进行服务费的CalCulate()?

这里我们专注考虑下两个设计点

  • 实体Task是否包含计算的逻辑
  • 计算的逻辑怎么设计

ok,尝试演进下代码的设计。


面向过程

可能写出如下的流水代码,肯定是不可取的。

  • 代码耦合,无法拓展,当计算逻辑变化时,可能影响到其他业务逻辑
  • 没法对计算逻辑进行单元测试
/*** 派发任务* @param request*/public void distributeTask(Request request) {check(request);。。。//任务实体Task task = generateTask(request);//计算服务费BigDecimal serviceFee = task.getTaskMoney().multiply(task.getServiceFeeRate()).setScale(2, RoundingMode.HALF_UP);task.setServiceFee(serviceFee);。。。。//保存任务save(task);。。。。}

封装计算进Task

第一步尝试将计算服务费逻辑封装进Task类中,是否有问题

@Data
public class Task {/*** 任务名称*/private String taskName;/*** 任务Id*/private String taskId;/*** 任务金额*/private BigDecimal taskMoney;/*** 服务费率*/private BigDecimal serviceFeeRate;/*** 服务费*/private BigDecimal serviceFee;/*** 计算服务费*/public void calculateServiceFee() {BigDecimal serviceFee = this.taskMoney.multiply(this.getServiceFeeRate()).setScale(2, RoundingMode.HALF_UP);this.setServiceFee(serviceFee);}
}

这样我们客户端service的调用就会是这样的

	 	  	//任务实体Task task = generateTask(request);//当然这一步是可以放在generateTask()方法中。//task.calculateServiceFee();

看起来service的调用是清晰了,但这是有代价的,我们要做的是降低代价,做到平衡。

代价就是Task类内多了一个职责,即计算逻辑的维护;

理论上Task类应当只会在一种情况下会变更:属性的变更,比如说增加一个属性TaskDescription

违反了solid中的第一原则:单一职责。


封装计算进Calculator

既然service与实体Task承担不来他们不该应有的压力:Calculate,那我们来个压力转移。

定义一个计算器Calculator,专注于计算服务费。

Task可以定义为实体类/业务类,一定是有状态的

Calculator 定义为操作类/辅助类,无状态stateless

注意这两者是完全不同的

设计Calculator 有以下几种方案:

1.持有计算所需属性(有状态)

public class Calculator {private BigDecimal taskMoney;private BigDecimal serviceFeeRate;/*** 计算服务费* @return 计算结果*/public BigDecimal calculateServiceFee() {return this.taskMoney.multiply(serviceFeeRate).setScale(2, RoundingMode.HALF_UP);}
}

缺点很明显

1.当Task属性变化时,此辅助类也跟着变化

2.线程不安全,多线程当多个Task需要计算时,会存在计算的值相互覆盖的场景

2.持有整个计算对象(有状态)

public class Calculator {private Task task;/*** 计算服务费* @return 计算结果*/public BigDecimal calculateServiceFee() {return task.getServiceFee().multiply(task.getServiceFeeRate()).setScale(2, RoundingMode.HALF_UP);}
}

这种方案控制了Task变化时影响的Calculator 本身属性的变化,但是仍然存在线程安全的问题

3.不持有对象无状态设计

public class Calculator {/*** 计算服务费* @return 计算结果*/public BigDecimal calculateServiceFee(Task task) {return task.getServiceFee().multiply(task.getServiceFeeRate()).setScale(2, RoundingMode.HALF_UP);}
}

这里的无状态设计我们保证了线程安全,又保证了单一职责,算是比较完善的情况。

4.面向接口的可拓展的无状态设计

对于上述的设计,我们可以通过继承来将具体的行为封装到子类中去。

4.1定义一个接口

public interface ICalculator<T> {BigDecimal calcualate(T t);
}

4.2 具体的计算服务类

比如说回到我们最初的需求,需要一个计算服务费的类,使用ServiceFeeCalculator 实现ICalculator

如果需要新增其他计算类,比如说计算薪水的类,再增加一个具体的SalaryCalculator 即可。

完全做到了和业务逻辑的分离,使ICalculator是一个可拓展的无状态类.

/*** @author hailang.zhang* @since 2023-11-21*/
public class ServiceFeeCalculator implements ICalculator<Task{@Overridepublic BigDecimal calcualate(Task task) {return task.getServiceFee().multiply(task.getServiceFeeRate()).setScale(2, RoundingMode.HALF_UP);;}
}

4.3 简单工厂模式

甚至更近一步,根据开闭原则,我们将获取ICalculator的逻辑委托给工厂,service进一步做到计算逻辑的无感,甚至无需自己对计算方法进行选择。


public class CalculatorFactory {public static ICalculator calculate(String type) {if (type.equals("sercviceFee")) {return new ServiceFeeCalculator();} else if (type.equals("salalry")) {return new SalaryCalculator();} else if ()......省略}
}

当然上述的工厂模式可以进一步的优化,有兴趣的可以看我之前的一篇文章

使用工厂、模板、策略模式重构代码

最终在service使用的时候效果

public void distributeTask(Request request) {。。。//任务实体Task task = generateTask(request);//计算服务费(此处可以将简单工厂模式进一步的优化)**BigDecimal serviceFee = CalculatorFactory.calculate("serviceFee").calcualate(task);**。。。。//保存任务save(task);。。。。}

代码演进中做了什么

  • 从功能层面区分有状态和无状态的类 Task/Calculator
  • 设计无状态的类Calculator
  • 进一步抽象出接口ICalculator,面向接口
  • 进一步解耦CalculatorFactory ,封装算法

学到了什么

无状态的类yyds,但要明确的是有状态的业务类是不可避免的,比如Task,但我们可以将部分辅助功能拆分出来,比如**Calculator。尽量去保证业务类的单一性,而且无状态的设计会非常轻量级,无需去维护线程安全。

对于Calculator(计算器)辅助类的命名尽量精确,可以体现其本身的功能,可以出现类似Importor(导入器)、Register(注册器),如果出现HandlerProcessor,我认为过于模糊,还是有待商榷的。

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

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

相关文章

VMware——WindowServer2012R2环境安装mysql5.7.14解压版_主从复制(图解版)

目录 一、服务器信息二、192.168.132.33主服务器上安装mysql&#xff08;主&#xff09;2.1、环境变量配置2.2、安装2.2.1、修改配置文件内容2.2.2、初始化mysql并指定超级用户密码2.2.3、安装mysql服务2.2.4、启动mysql服务2.2.5、登录用户管理及密码修改2.2.6、开启远程访问 …

Redis 9 数据库

4 设置键的生存时间或过期时间 通过EXPIRE命令或者PEXPIRE命令&#xff0c;客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间&#xff08;TimeToLive&#xff0c;TTL&#xff09;&#xff0c;在经过指定的秒数或者毫秒数之后&#xff0c;服务器就会自动删除生存时间…

SPASS-时间序列预测

时间序列的建立和平稳化 填补缺失值 时间序列分析中的缺失值不能采用通常删除的办法来解决&#xff0c;因为这样会导致原有时间序列周期性的破坏&#xff0c;而无法得到正确的分析结果。按“转换→替换缺失值”打开“替换缺失值”对话框。 定义日期变量 展示效果如下

Vue3 customRef自定义ref 实现防抖

防抖就是防止在input 框中每输入一个字符就要向服务器请求一次&#xff0c;只要在用户输入完成过一段时间再读取用户输入的内容就能解决这个问题&#xff0c;减小服务器的压力。 1. 自定义ref是一个函数&#xff0c;可以接受参数。 比如我们自定义一个myRef&#xff1a; setu…

CSDN每日一题学习训练——Python版(搜索插入位置、最大子序和)

版本说明 当前版本号[20231118]。 版本修改说明20231118初版 目录 文章目录 版本说明目录搜索插入位置题目解题思路代码思路参考代码 最大子序和题目解题思路代码思路参考代码 搜索插入位置 题目 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;…

全网最全jmeter接口测试/接口自动化测试看这篇文章就够了:跨线程组传递jmeter变量及cookie的处理

setUp线程组 setUp thread group&#xff1a; 一种特殊类型的线程组&#xff0c;用于在执行常规线程组之前执行一些必要的操作。 在 setup线程组下提到的线程行为与普通线程组完全相同。不同的是执行顺序--- 它会在普通线程组执行之前被触发&#xff1b; 应用场景举例&#xf…

转录组学习第三弹-下载SRR数据并转成fastq

下载数据 前面已经安装好了需要的软件&#xff0c;那么我们现在需要下载我们练习需要用到的sra数据。从 SRA 数据库下载数据有多种方法。可以用ascp快速的来下载 sra 文件&#xff0c;也可以用wget或curl等传统命令从 FTP 服务器上下载 sra 文件。另外sra-tools的prefetch也支…

JavaScript-变量类型

更多内容&#xff0c;请访问 声明和定义区别 JavaScript-变量类型判断 JavaScript-如何使用变量 JavaScript-undefined和null区别 JavaScript变量类型有6种&#xff08;新增加的Symbol、BigInt&#xff0c;八种&#xff09;&#xff0c;又将分为两大类&#xff1a;基础数据类…

Java精品项目源码基于SpringBoot的樱花短视频平台(v66)

Java精品项目源码基于SpringBoot的樱花短视频平台(v66) 大家好&#xff0c;小辰今天给大家介绍一个樱花短视频平台&#xff0c;演示视频公众号&#xff08;小辰哥的Java&#xff09;对号查询观看即可 文章目录 Java精品项目源码基于SpringBoot的樱花短视频平台(v66)难度指数&…

基于单片机加热炉多参数检测和PID炉温系统

**单片机设计介绍&#xff0c; 基于单片机加热炉多参数检测和PID炉温系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的公交安全预警系统可以被设计成能够实时监测公交车辆的行驶状态&#xff0c;并在发生异常情况…

linux中编写.sh脚本并赋权限问题

以项目启动、重启、终止脚本为例&#xff1a; 步骤&#xff1a; 首先vi start.sh、 vi restart.sh、 vi stop.sh或者使用vim编辑器&#xff1b; 编辑内容&#xff1a; 启动&#xff1a;vi start.sh #!/bin/bash nohup java -jar jeewx-boot-start-1.0.0.jar >catalina.…

Softing mobiLink助力过程自动化——兼容HART、FF、PA的多协议接口工具

由于全球人口增加和气候变化等因素&#xff0c;“水”比以往任何时候都更具有价值。与此同时&#xff0c;环境法规和水处理标准也变得愈加严格。在这一大环境下&#xff0c;自来水公司不得不应对一些新的挑战&#xff0c;例如&#xff0c;更好地提高能源效率、最大程度地减少资…

HP惠普暗影精灵7Plus笔记本OMEN 17.3英寸游戏本17-ck0000恢复原厂Windows11预装OEM系统

链接&#xff1a;https://pan.baidu.com/s/1ukMXI2V3D0c-kVmIQSkbYQ?pwd2rbr 提取码&#xff1a;2rbr hp暗影7P原厂WIN11系统适用型号&#xff1a; 17-ck0056TX&#xff0c; 17-ck0055TX&#xff0c; 17-ck0054TX &#xff0c;17-ck0059TX 自带所有驱动、出厂时主题壁纸、…

vue-quill-editor 使用

vue-quill-editor 安装 npm install vue-quill-editor -S 使用 .....<quill-editorstyle"padding-left: 0;padding-top: .0px;margin-top: 30px;"ref"editorRef" v-model"params.content" class"ql-editor" :options"editor…

【算法每日一练]-旅行商(保姆级教程 篇1)

题目&#xff1a;TSP旅行商 旅行商问题&#xff0c;即TSP问题&#xff08;Traveling Salesman Problem&#xff09;又译为旅行推销员问题、货郎担问题&#xff0c;是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市&#xff0c;他必须选择所要走的路径&#xff0c;路…

大数据:SAS数据分析1,数据步,和过程步

大数据&#xff1a;SAS数据分析 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sql…

java.lang.UnsupportedOperationException 关于Arrays.asList问题解决

解析String 字符串为List集合ArrayList<String> itemsList Arrays.asList(items.split("\\|")List<String> itemsList Arrays.asList(items.split("\\|")final Iterator<String> iterator itemsList.iterator();while (iterator.hasNex…

自定义类型之结构体

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

Altium Designer学习笔记5

整体修改元件标号&#xff1a; 重置Reset Schematic Designators: 恢复之前的状态。复位&#xff0c;恢复之前的状态。

安防视频监控管理平台EasyCVR定制首页开发与实现

视频监控平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#xff0c;也能支持视…