基于多种设计模式重构代码(工厂、模板、策略)

基于多种设计模式重构代码

现状

  系统目前支持三种业务流程,业务A, 业务B,业务C,每个流程有相同的业务逻辑,也包含很多的特性化业务。由于之前业务流程的开发是快速迭代的,而且迭代了很多次,开发同学们应该可以理解,过程中免不了面向过程、CV大法…现在无论是业务发展,还是我们产研团队对于业务的理解,都趋于稳定,是时候该还债了,重构代码,我辈义不容辞!

恰好,新需求来了,需要在原有业务B流程中增加一个业务环节,申请支付:业务发起时扣减对应客户的资产余额。

业务流程A抽象后伪码如下:

public void applyPay(Request request) {//数据查询query();//数据校验doCheck(request);//业务逻辑处理if (request.getFlag()) {//资产校验//扣减资产//生成审批流数据//推送钉钉processA();} else {//资产校验//扣减资产//生成审批流数据//推送钉钉processB();}//数据更新update();}

更巧的是之前业务A中做过类似的业务,而且需求评估的时候产品信誓旦旦的说业务B、C中肯定不会有这个业务。

可以预见的是业务C中将来也会有这个业务。

Don not Repeat yourself,这个逻辑公共化处理是必要的。


模板模式

对于上述业务流程,很容易想到模板模式对代码进行通用性抽象优化:

定义算法步骤骨架,并允许子类对一个或多个步骤进行实现

  • 定义模板流程
  • 定义抽象方法,子类强制实现
  • 如有必要定义钩子方法

模板模式很容易理解。

定义一个接口类

public interface IApplyPayProcessor {void applyPay(ApplyPayRequest request);
}

定义抽象父类

public abstract class AbstractApplyPayProcessor<T> implements IApplyPayProcessor{/*** 申请支付模板方法* @param request*/@Overridepublic void applyPay(ApplyPayRequest request) {//数据查询T t= query(request);//校验check(t);//业务逻辑处理process(t, request);//数据保存save(t);//通用业务逻辑处理sendMsg(t);}private void sendMsg(T t) {//发送业务逻辑完成通知 event、msg......实际代码省略}/*** 根据业务,子类实现* @param request* @return*/public abstract T query(ApplyPayRequest request);/*** 根据业务,子类实现* @param t* @return*/public abstract Boolean check(T t);/*** 根据业务,子类实现* @param t* @param request*/public abstract void process(T t, ApplyPayRequest request);/*** 根据业务,子类实现* @param t*/public abstract void save(T t);
}

子类实现

对于业务A,继承AbstractApplyPayProcessor,并根据自身业务的逻辑,实现相应的方法。

对于业务B、业务C也是类似的代码,只需要实现相应的方法,无需对业务流程进行重新的编排,毕竟父类已经定义了骨架方法。

public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{@Overridepublic BizADO query(ApplyPayRequest request) {return new BizADO();}@Overridepublic Boolean check(BizADO o) {//..子类业务逻辑省略return Boolean.TRUE;}@Overridepublic void process(BizADO o, ApplyPayRequest request) {//..子类业务逻辑省略}@Overridepublic void save(BizADO o) {//..子类业务逻辑省略}
}

工厂模式

经过第一步模板模式优化后,我们该怎么使用呢

通常我们会在service层这样使用:

public void doApplyPay(ApplyPayRequest request) {....其他业务逻辑if (request.getType().equals(A)) {BizAApplyPayProcessor processor = new BizAApplyPayProcessor();processor.applyPay(request);} else if (request.getType().equals(B)) {BizBApplyPayProcessor processor = new BizBApplyPayProcessor();processor.applyPay(request);} else if (request.getType().equals(C)) {BizCApplyPayProcessor processor = new BizCApplyPayProcessor();processor.applyPay(request);}..... 其他业务逻辑}

我们很大概率会按照上述代码的编写方式,先通过if/else判断,然后使用new这个关键字,实例化对应的实体,或者Spring的方式注入对应的方式。

new代表着实例化,意味着我们的代码需要依赖具体的实现,而通常推荐的编程是面向接口而不是面向实现。

当有新的类型,比如业务D被添加进来时,这个service需要修改,if/else逻辑需要改变,不符合开闭原则,设计模式的核心的一点就是封装可变的部分。

可以使用工厂模式if/else封装在单独的类,伪代码如下:

public class BizFactory {public static IApplyPayProcessor getApplyPayProcessor(String bizType) {IApplyPayProcessor processor;if (A) {processor = new BizAApplyPayProcessor();processor.applyPay(request);} else if (B) {processor = new BizBApplyPayProcessor();processor.applyPay(request);} else if (C) {processor = new BizCApplyPayProcessor();processor.applyPay(request);}return processor;}
}//service层使用工厂类直接获取对应的实例
public class ClientService {public void doApplyPay(ApplyPayRequest request) {BizFactory.getApplyPayProcessor(request.getType());}
}

这样做起来看起来只是将代码从service层移动到了一个BizFactory中,问题依然是存在的,有什么好处呢?

  • service层不再随着实现的变化而变化,尽量往单一职责去靠拢,注意我这里说的是尽量,因为往往我们会做一个取舍。
  • BizFactory可以有很多业务工厂,其他业务service以后也不需要变化,只需要变化BizFactory

策略模式

我们可以看到对于工厂本身而言,虽然我们将对象的使用和创建已经分离开来,使用工厂模式来专注于对象的创建,当新增业务类型时,比如业务D时,我们需要修改工厂模式,不符合开闭原则

如何进行进一步优化呢,让工厂模式不随着业务类型的增加而进行修改呢。

答案即是使用策略模式

定义一系列的算法或策略,并将每个算法封装在单独的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

step1 先定一个枚举BizTypeEnum 用来标识业务类型,也就是策略的集合。

@Getter
public enum BizTypeEnum {A(1, "业务A"),B(2, "业务B"),C(3, "业务C"),D(4, "业务D"),;private int code;private String msg;BizTypeEnum(int code, String msg) {this.code = code;this.msg = msg;}/*** 整形值转换为枚举类** @param value 值* @return 枚举类*/public static BizTypeEnum valueOf(int value) {for (BizTypeEnum anEnum : values()) {if (value == anEnum.getCode()) {return anEnum;}}return null;}
}

step2 定义一个注解,ApplyPay用来标记策略类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplyPay {BizTypeEnum bizType() default BizTypeEnum.A;
}

step3 使用注解@ApplyPay,改造标记策略类,

@ApplyPay(bizType = BizTypeEnum.A)
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{@Overridepublic BizADO query(ApplyPayRequest request) {return new BizADO();}@Overridepublic Boolean check(BizADO o) {//..子类业务逻辑省略return Boolean.TRUE;}@Overridepublic void process(BizADO o, ApplyPayRequest request) {//..子类业务逻辑省略}@Overridepublic void save(BizADO o) {//..子类业务逻辑省略}
}@ApplyPay(bizType = BizTypeEnum.B)
public class BizBApplyPayProcessor extends AbstractApplyPayProcessor<BiZBDO>{@Overridepublic BiZBDO query(ApplyPayRequest request) {return null;}@Overridepublic Boolean check(BiZBDO biZBDO) {return null;}@Overridepublic void process(BiZBDO biZBDO, ApplyPayRequest request) {}@Overridepublic void save(BiZBDO biZBDO) {}
}

step4 识别加载封装策略类,改造工厂类

可以借助Spring的拓展能力,识别策略类的注解,并将策略实例化后,使用集合保存,当然如果没有使用Spring框架,也可以借助反射等来进行此动作。

public class BizFactory implements ApplicationListener<ContextRefreshedEvent> {private static Map<BizTypeEnum, IApplyPayProcessor> APPLY_PAY_MAP = new ConcurrentHashMap<>(8);public static IApplyPayProcessor getApplyPayProcessor(BizTypeEnum bizType) {return APPLY_PAY_MAP.get(bizType);}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {ApplicationContext applicationContext = event.getApplicationContext();//申请支付工厂初始化Map<String, Object> beansWithAnno = applicationContext.getBeansWithAnnotation(ApplyPay.class);if (beansWithAnno != null) {beansWithAnno.forEach((key, value) -> {BizTypeEnum bizType = value.getClass().getAnnotation(ApplyPay.class).bizType();APPLY_PAY_MAP.put(bizType, (IApplyPayProcessor) value);});}}
}

使用策略模式改造完之后,显而易见的改造后,不同的业务被封装在不同的类中,工厂模式也无需知晓每个类,只需要根据业务类型加载即可。

每次新增业务类型时,只需要新增策略类即可,无需对servicefactory进行修改。

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

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

相关文章

【邻接表特点,邻接表的代码实现】

文章目录 邻接表特点邻接矩阵和邻接表表示法的关系 邻接表&#xff08;Adjacency List&#xff09; 是图的一种链式存储结构。 #include<iostream> using namespace std;#define MVNum 100 //最大顶点数 #define OtherInfo int //权值 #define VerTexType int//顶点的指针…

一道好题——分治

一道好题应该有一个简洁的题面。 有一个长度为 n&#xff0c;初始全为 0 的序列 a&#xff0c;另有一个长度为 n 的序列 b&#xff0c;你希望将 a 变成 b&#xff0c;你可以执行如下两种操作&#xff1a; 1 x&#xff1a;将 a 中所有值为 x 的数 11。 2 x&#xff1a;将 a 中下…

Android开发APP显示头部Bar

Android开发显示头部Bar 需求&#xff1a; 显示如下图&#xff1a; 显示头部Bar&#xff0c;颜色也能自定义。 解决方案 这个修改是在如下三个文件里进行修改&#xff1a; 按顺序修改&#xff1a; themes.xml(night): <resources xmlns:tools"http://schemas.andr…

持续集成交付CICD:Jenkins Sharedlibrary 共享库

目录 一、理论 1.共享库 2.共享库配置 3.使用共享库 4.共享库扩展 二、实验 1.连接共享库 2.使用共享库 三、问题 1.路径报错 2.readJSON 报错 一、理论 1.共享库 &#xff08;1&#xff09;概念 1&#xff09;共享库这并不是一个全新的概念&#xff0c;其实在编…

央国企数字化转型难在哪?为什么要数字化转型?

科技在发展&#xff0c;技术在升级&#xff0c;全球信息化、数字化的步伐在加快&#xff0c;企业想要在未来的发展中抓住机会&#xff0c;更好地发展壮大&#xff0c;就需要加快企业数字化转型的速度&#xff0c;才能立足于信息化、数字化时代&#xff0c;央国企作为企业中的一…

Vue3 项目修改index.html的 title

实现思路 通过插件 vite-plugins-html 进行参数配置&#xff0c;html 中使用参数&#xff0c;实现配置安装插件 $ npm install vite-plugins-html --save-devvite.config.js 中的配置 // 可以动态处理html文件内容的 import { createHtmlPlugin } from vite-plugin-htmlexpo…

Echarts 实现两两柱图重叠(背景和实际值柱图)

Echarts实现两两重叠柱状图_echarts 重叠柱状图_Web_阿凯的博客-CSDN博客 引用启发的博客 先来效果&#xff1a; option {backgroundColor: #03213D,animation: true, // 控制动画是否开启animationDuration: 1000, // 动画的时长, 它是以毫秒为单位animationDuration: func…

KaiwuDB 监控组件及辅助 SQL 调优介绍

一、介绍 KaiwuDB 具备完善的行为数据采集功能&#xff0c;此功能要求 KaiwuDB 数据库系统 C/E/T 端不同进程的不同维度的指标采集功能十分完善&#xff1b;在不同进程完成指标采集后&#xff0c;会通过 Opentelemetry 和 Collector 将指标存入 Prometheus&#xff0c;以便查找…

KiCad源代码研究:KiCad是如何渲染和绘图的。

common.json文件中appearance.show_scrollbars common.json对应于代码的common_settings 1.EDA_DRAW_PANEL_GAL类 EDA_DRAW_PANEL_GAL类中定义了绘图的基本要素&#xff1a; /// Interface for drawing objects on a 2D-surfaceKIGFX::GAL* m_gal;/// Stores v…

主播三维能力总览

主播需要的三维能力包括话术能力、成交能力和表现力 主播的话术能力 主播在直播过程中运用语言和表达方式的能力。好的话术能力可以帮助主播吸引观众、增加互动、提高直播效果等。 1.了解观众需求:在直播前&#xff0c;主播应该了解观众的需求和兴趣&#xff0c;以便在直播过…

小程序商城免费搭建之java商城 电子商务Spring Cloud+Spring Boot+二次开发+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

Scala--- Actor通信模型

一、概念理解 1、Java中的并发编程 Java中的并发编程是基于共享数据和加锁的一种机制&#xff0c;即会有一个共享的数据&#xff0c;然后有若干个线程去访问这个共享的数据(主要是对这个共享的数据进行修改)&#xff0c;同时Java利用加锁的机制(即synchronized)来确保同一时间…

98.qt qml-使用曲线图综合示例、支持多种鼠标交互、支持百万数据显示(已适配黑白风格)

在上章我们只是简单实现了曲线图和折线图的显示: 79.qt qml-如何在QML中使用QCustomPlot之曲线/折线示例(已适配黑白风格)_qml 折线图_诺谦的博客-CSDN博客 所以本章实现综合示例、并添加多种功能如下所示: 详细显示:鼠标任意移动显示具体值内容鼠标右击: 弹出菜单栏,支持…

记一次解决Pyqt6/Pyside6添加QTreeView或QTreeWidget导致窗口卡死(未响应)的新路历程,打死我都想不到是这个原因

文章目录 💢 问题 💢🏡 环境 🏡📄 代码💯 解决方案 💯⚓️ 相关链接 ⚓️💢 问题 💢 我在窗口中添加了一个 QTreeWidget控件 ,但是程序在运行期间,只要鼠标进入到 QTreeWidget控件 内进行操作,时间超过几秒中就会出现窗口 未响应卡死的 状态 🏡 环境 �…

智慧校园烟火识别及预警解决方案,保障校园消防安全

一、方案背景 校园消防安全一直以来是社会各界备受关注的问题。为了保障师生的人身安全和财产安全&#xff0c;越来越多的学校开始引入AI智能检测技术&#xff0c;通过运用AI智能烟火检测技术&#xff0c;对学校的周界、教室、走廊、公共区域、教学楼、食堂等场所进行安全监测…

世微 电动车摩托车灯 5-80V 1.2A 一切二降压恒流驱动器AP2915

产品描述 AP2915 是一款可以一路灯串切换两路灯串的降压恒流驱动器,高效率、外围简单、内置功率管&#xff0c;适用于5-80V 输入的高精度降压 LED 恒流驱动芯片。内置功率管输出最大功率可达 12W&#xff0c;最大电流 1.2A。AP2915 一路灯亮切换两路灯亮&#xff0c;其中一路灯…

【c#】使用Prometheus监控Windows系统

Prometheus 监控Windows机器 1&#xff0e;Prometheus 简单介绍 Prometheus 是一个开源的系统监控和报警工具包,最初建立在 SoundCloud 之上。自 2012 年成立以来,许多公司和组织都采用了 Prometheus,它拥有非常活跃的开发者和用户社区。现在是一个独立的开源项目,独立于任何…

Leetcode 153. 寻找旋转排序数组中的最小值

class Solution {//因为最小值和最大值总是相邻的&#xff08;除了初始状态&#xff09;//1.用二分查找&#xff0c;如果右侧是有序则最小值在左侧//2.如果右侧无序则最小值在右侧//如果mid正好是最小值&#xff0c;那么右侧自然是有序的&#xff0c;//为了将mid加入到搜索的一…

你想对构造函数说些什么?

回顾知识 在学习类的默认成员函数之前&#xff0c;先带大家复习一下什么是类&#xff0c;类是在C中引进的新的类型&#xff0c;是一种自定义类型&#xff0c;实际上跟C语言中的结构体类似&#xff0c;但是是对结构体的升级&#xff0c;可以在类里面添加函数&#xff0c;也有对应…