当前位置: 首页 > news >正文

设计模式的使用——模板方法模式+动态代理模式

一、需求介绍

  现有自己写的的一套审批流程逻辑,由于代码重构,需要把以前的很多业务加上审批的功能,再执行完审批与原有业务之后,生成一个任务,然后再统一处理一个任务(本来是通过数据库作业去处理的,后来说这个任务要马上去处理,只能去统一添加一个处理任务的逻辑,去手动触发作业,心里1w只草泥马在欢快的奔腾着)。现有的问题是:

  • 如何将原有的业务逻辑和审批流程给统一整合,以减少工作量
  • 如何统一添加处理任务的功能

二、设计模式选择

  • 模板方法(Template Method)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。我们可以根据这个模式去统一整合业务逻辑和审批流程,将整合的逻辑模板里面。
  • 动态代理()模式:通过动态代理模式对生成任务的方法统一增强,在生成这个任务之后,立马去执行这个任务。

三、代码实现

1、准备工作

项目的目录结构如下:

image.png

在开始之前,需要规范一些常量

  • 成功状态表示枚举类:
public enum CommonResult {SUCCESS("200", "success","成功","成功"),FAIL("201","fail","失败","失败");private final String code;private final String status;private final String msg;private final String data;CommonResult(String code, String status, String msg, String data) {this.code = code;this.status = status;this.msg = msg;this.data = data;}public String getCode() {return code;}public String getStatus() {return status;}public String getMsg() {return msg;}public String getData() {return data;}}
  • 新建审批异常类:
public class ApproveException extends RuntimeException {public ApproveException(String message){super(message);}public ApproveException(Throwable throwable){super(throwable);}
}
  • 新建常量类,表示不同审批结果,不同审批返回值需要处理不同的业务逻辑:
public interface Constants {//不执行插入任务 与 执行任务的逻辑String FLOW_STATUS1 = "1";//只执行审批逻辑String FLOW_STATUS2 = "2";//不执行业务逻辑String FLOW_STATUS3 = "3";//执行所有逻辑String FLOW_STATUS4 = "4";
}
  • 封装审批参数类,用来规范审批值传递,通过这个类规定需要审批接口需要的参数:

/*** @description: 请求审批* @create: 2020-04-24 13:40**/
public class ApproveDTO {//审批意见private String suggestion;//0 驳回 1 同意private Integer appType;//流程idprivate Integer approveId;//当前员工idprivate String empId;//当前员工nameprivate String empName;//当前角色IDprivate String roleId;public String getSuggestion() {return suggestion;}public void setSuggestion(String suggestion) {this.suggestion = suggestion;}public Integer getAppType() {return appType;}public void setAppType(Integer appType) {this.appType = appType;}public Integer getApproveId() {return approveId;}public void setApproveId(Integer approveId) {this.approveId = approveId;}public String getEmpName() {return empName;}public void setEmpName(String empName) {this.empName = empName;}public String getEmpId() {return empId;}public void setEmpId(String empId) {this.empId = empId;}public String getRoleId() {return roleId;}public void setRoleId(String roleId) {this.roleId = roleId;}
}

2、编写审批逻辑

  这里只是一个简单地审批服务类,主要有两个功能:
① execApprove()方法:用来执行审批的节点流转等逻辑。
② runApproveTask()方法:这个是后来新加的添加任务后马上执行的需求,主要调用数据库的存储过程。

  • 审批服务接口:
public interface IApproveService {/*** 执行审批* @param approveDTO 审批信息* @return 0 失败 1 成功*/String execApprove(ApproveDTO approveDTO, Map<String,String> paramMap);/*** 执行审批存储过程*/void runApproveTask(Integer approveId);
}
```java
- 审批服务接口实现类
```java
public class ApproveServiceImpl implements IApproveService {@Overridepublic String execApprove(ApproveDTO approveDTO, Map<String, String> paramMap) {System.out.println(approveDTO.getEmpName()+"执行了"+(approveDTO.getAppType().equals(0)?"驳回":"通过")+"审批流程逻辑,因为:"+approveDTO.getSuggestion());return paramMap.get("logic");}@Overridepublic void runApproveTask(Integer approveId) {System.out.println("存储过程被执行......");}
}

3、封装审批模板

  这里主要处理业务逻辑和审批逻辑之间的关系:

  • 抽象类,主要定义了钩子方法要实现的功能,doApprove()方法:执行审批,处理业务逻辑和审批逻辑之间的关系就是这个方法:
public interface IApproveTemplate {String doApprove();}
  • 模板类,通用模板逻辑封装,扩展需要在业务逻辑中实现的功能(抽象方法),实现了IApproveTemplate的doApprove()方法(钩子方法):
/*** @author FluffyCatkin* @create: 2020-06-02 14:00**/
public abstract class CommonApproveTemplate implements IApproveTemplate {protected IApproveService approveService;protected ApproveDTO approveDTO;private final Map<String,String> paramMap;public CommonApproveTemplate(ApproveDTO approveDTO, Map<String,String> paramMap) {this.approveDTO = approveDTO;this.paramMap = paramMap;this.approveService = new ApproveServiceImpl();this.approveService = new ApproveServiceImpl();}@Overridepublic String doApprove(){String execResult = execApprove();String businessResult = "";//执行自定义业务if (Objects.equals(execResult,Constants.FLOW_STATUS1)||Objects.equals(execResult,Constants.FLOW_STATUS4)){businessResult = business();if (!businessResult.equals(CommonResult.SUCCESS.getStatus())){System.out.println("审批流程执行失败,开始回滚");}}//插入任务if (Objects.equals(execResult,Constants.FLOW_STATUS3)||Objects.equals(execResult,Constants.FLOW_STATUS4)){businessResult = taskBusiness();if (!businessResult.equals(CommonResult.SUCCESS.getStatus())){System.out.println("审批流程执行失败,开始回滚");}}//什么也不做if (Objects.equals(execResult,Constants.FLOW_STATUS2)){return CommonResult.SUCCESS.getStatus();}return businessResult;}/*** 执行审批流程节点流转逻辑* @return 不同的执行结果* 比如所有人审批通过或者流转到下一个审批人等等,这里不纠结具体 只用 1 2 3 4表示不同结果*/private String execApprove(){return approveService.execApprove(this.approveDTO,this.paramMap);}/*** 对传的审批参数进行解析校验,可以判断调用通过业务逻辑还是 拒绝的业务逻辑等等*/protected abstract String business();/*** 新增任务,不同的业务需要自定义自己的任务*/protected abstract String taskBusiness();}
  • 业务逻辑抽象类,定义了用户需要在自己的业务服务类中所实现的功能,这些功能不同的业务是不一样的:

public interface CommonApproveService {/*** 跟进审批同意操作* @param paramMap 参数*/String agreeApprove(Map<String, String> paramMap);/*** 跟进审批拒绝操作* @param paramMap 参数*/String refuseApprove(Map<String, String> paramMap);/*** 将审批操作放到任务中跑* @param paramMap 审批参数*/String insertApproveTask(Map<String, String> paramMap,Integer approveId);/*** 执行跟进的审批* @param approveDTO 审批流程改变需要的参数* @param paramMap 执行业务逻辑需要的参数* @return 审批结果*/String execApprove(CommonApproveService service,ApproveDTO approveDTO, Map<String, String> paramMap);}
  • 模板具体实现类,这里封装了用户定义的业务的通用服务类,这些服务类要统一实现上面的CommonApproveService接口:

/*** @author FluffyCatkin* @create: 2020-06-02 14:26**/public class ApproveConcrete<T extends CommonApproveService> extends CommonApproveTemplate {private final T service;private final Map<String, String> paramMap;public ApproveConcrete(ApproveDTO approveDTO, Map<String, String> paramMap, T service) {super(approveDTO, paramMap);this.service = service;this.paramMap = paramMap;}@Overrideprotected String business() {if(super.approveDTO==null){throw new ApproveException("审批失败,没有参数!");}Integer appType = super.approveDTO.getAppType();if (appType==null){throw new ApproveException("审批失败,未传入审批意见,请传入:1 同意  0 驳回!");}if (appType==0){return service.refuseApprove(paramMap);}if (appType==1){return service.agreeApprove(paramMap);}throw new ApproveException("审批失败,审批意见错误,请传入:1 同意  0 驳回!");}@Overrideprotected String taskBusiness() {if(super.approveDTO==null){throw new ApproveException("审批失败,没有参数!");}return service.insertApproveTask(paramMap,approveDTO.getApproveId());}}

4、业务逻辑服务实现

  • 定义服务接口,要继承封装的业务逻辑抽象类
public interface IBusinessService extends CommonApproveService {//在这里可以写业务逻辑相关方法
}
  • 定义服务接口实现类

public class BusinessServiceImpl implements IBusinessService {@Overridepublic String agreeApprove(Map<String, String> paramMap) {System.out.println("执行同意业务逻辑");return CommonResult.SUCCESS.getStatus();}@Overridepublic String refuseApprove(Map<String, String> paramMap) {System.out.println("执行拒绝业务逻辑");return CommonResult.SUCCESS.getStatus();}@Overridepublic String insertApproveTask(Map<String, String> paramMap, Integer approveId) {System.out.println("执行插入任务业务逻辑");return CommonResult.SUCCESS.getStatus();}@Overridepublic String execApprove(CommonApproveService service,ApproveDTO approveDTO, Map<String, String> paramMap) {System.out.println("开始执行审批....");return new ApproveUtil<> (service).execApprove(approveDTO, paramMap);}
}

5、通过动态代理对业务逻辑服务类进行增强,加上立刻执行存储过程的逻辑:

  • 代理类:

/*** @author FluffyCatkin* @version 1.0* @date 2020/1/3 0003 10:04* @description (动态代理)代理(Proxy)类:对真实主题功能的扩展*/
public class DynamicProxy implements InvocationHandler {//需要代理的对象private final Object  dynamicSubject;private final IApproveService approveService = new ApproveServiceImpl();private static final String  PROXY_METHOD = "insertApproveTask";/*** 构造方法* @param dynamicSubject 要代理的对象*/public DynamicProxy(Object dynamicSubject){this.dynamicSubject = dynamicSubject;}/**** @param proxy 被代理的类* @param method 要增强的方法* @param args 增强的方法参数* @return 增强的方法返回值* @throws Throwable 异常*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = method.invoke(dynamicSubject, args);if (PROXY_METHOD.equals(method.getName())){approveService.runApproveTask(null);}return invoke;}/*** 获取被代理的对象* @return 被代理的对象*/public Object getCommonApproveService() {return dynamicSubject;}/*** 获取代理后增强的对象* @param dynamicSubject 被代理的对象* @return 代理后增强的对象*/public static  CommonApproveService newInstance(Object dynamicSubject) {InvocationHandler invocationHandler = new DynamicProxy(dynamicSubject);return (CommonApproveService) Proxy.newProxyInstance(dynamicSubject.getClass().getClassLoader(),dynamicSubject.getClass().getInterfaces(),invocationHandler);}
}

6、测试


public class MainTest {//对业务的服务类进行增强,在spring中,可以通过spring配置,获取所有CommonApproveService的子类,并统一进行增强//然后通过@Autowired注解直接拿到增强对的服务类private final CommonApproveService businessService = DynamicProxy.newInstance(new BusinessServiceImpl());@Testpublic void testPassApprove() {ApproveDTO approveDTO = new ApproveDTO();approveDTO.setAppType(1);approveDTO.setEmpName("张三");approveDTO.setSuggestion("你长得太好看了");//这里是业务执行需要的参数,不同的业务需要不同的参数,这里使用map封装Map<String, String> paramMap = new HashMap<>();//通过这里指定审批流程的流转结果,从而控制其流转到不同的业务逻辑里,实际情况是根据审批节点流转结果判断的
//        paramMap.put("logic", Constants.FLOW_STATUS1);
//        paramMap.put("logic", Constants.FLOW_STATUS2);
//        paramMap.put("logic", Constants.FLOW_STATUS3);paramMap.put("logic", Constants.FLOW_STATUS4);String res = businessService.execApprove(businessService, approveDTO, paramMap);System.out.println(res);}
}

执行结果:
image.png

  注意:这里简化了很多逻辑,也可以结合项目进行改造,比如:可以在spring中,可以通过spring配置,获取所有CommonApproveService的子类,并统一进行增强,然后通过@Autowired注解直接拿到增强对的服务类。

相关文章:

设计模式的使用——模板方法模式+动态代理模式

一、需求介绍 现有自己写的的一套审批流程逻辑&#xff0c;由于代码重构&#xff0c;需要把以前的很多业务加上审批的功能&#xff0c;再执行完审批与原有业务之后&#xff0c;生成一个任务&#xff0c;然后再统一处理一个任务&#xff08;本来是通过数据库作业去处理的&#x…...

C++学习记录——삼십 智能指针

文章目录 1、为什么需要智能指针&#xff1f;2、内存泄漏3、智能指针的使用及原理1、RAII思想2、拷贝问题1、unique_ptr2、shared_ptr1、多线程2、循环引用3、定制删除器 1、为什么需要智能指针&#xff1f; 看一个场景 int div() {int a, b;cin >> a >> b;if (b…...

插件式架构 与 ReSharper、Visual Studio的故事

文章首发地址 ReSharper和Visual Studio的故事 ReSharper是一款由JetBrains公司开发的Visual Studio插件&#xff0c;它主要用于提高Visual Studio的开发效率和改善代码质量。ReSharper在早期的版本中被称为"Omea Code"&#xff0c;它最初是JetBrains一个研究项目的…...

Python UDP编程

前面我们讲了 TCP 编程&#xff0c;我们知道 TCP 可以建立可靠连接&#xff0c;并且通信双方都可以以流的形式发送数据。本文我们再来介绍另一个常用的协议--UDP。相对TCP&#xff0c;UDP则是面向无连接的协议。 UDP 协议 我们来看 UDP 的定义&#xff1a; UDP 协议&#xff…...

结构体(个人学习笔记黑马学习)

1、结构体的定义和使用 #include <iostream> using namespace std; #include <string>struct Student {string name;int age;int score; }s3;int main() {//1、struct Student s1;s1.name "张三";s1.age 18;s1.score 100;cout << "姓名&a…...

小白带你学习linux的PXE装机

目录 目录 一、PXE是什么&#xff1f; 二、PXE的组件&#xff1a; 1、vsftpd/httpd/nfs 2、tftp 3、dhcp 三、配置dhcp 1、关闭防火墙与selinux和配置本地yum源 2、安装dhcp服务 3、配置dhcp配置文件 四、配置vsftpd 五、配置tftp 1、安装tftp-server 2、启动tft…...

华为鲲鹏服务器

1.简介 鲲鹏通用计算平台提供基于鲲鹏处理器的TaiShan服务器、鲲鹏主板及开发套件。硬件厂商可以基于鲲鹏主板发展自有品牌的产品和解决方案&#xff1b;软件厂商基于openEuler开源OS以及配套的数据库、中间件等平台软件发展应用软件和服务&#xff1b;鲲鹏开发套件可帮助开发…...

Python金币小游戏

游戏规则&#xff1a;移动挡板接住金币 游戏截图&#xff1a; 详细代码如下&#xff1a; import pygame.freetype import sys import randompygame.init() screen pygame.display.set_mode((600, 400)) pygame.display.set_caption(game) p 0 i1 0 s 0 t 0 f1 pygame.f…...

Modbus转Profinet网关在大型自动化仓储项目应用案例

在自动化仓储项目中&#xff0c;Modbus是一种常见的通信协议&#xff0c;用于连接各种设备&#xff0c;例如传感器、PLC和人机界面。然而&#xff0c;Modbus协议只支持串行通信&#xff0c;并且数据传输速度较慢。为了提高通信效率和整体系统性能&#xff0c;许多大型仓储项目选…...

Java 并发 ThreadLocal 详解

文章首发于个人博客&#xff0c;欢迎访问关注&#xff1a;https://www.lin2j.tech 简介 ThreadLocal 即线程本地变量的意思&#xff0c;常被用来处理线程安全问题。ThreadLocal 的作用是为多线程中的每一个线程都创建一个线程自身才能用的实例对象&#xff0c;通过线程隔离的…...

JWT 技术的使用

应用场景&#xff1a;访问某些页面&#xff0c;需要用户进行登录&#xff0c;那我们如何知道用户有没有登录呢&#xff0c;这时我们就可以使用jwt技术。用户输入的账号和密码正确的情况下&#xff0c;后端根据用户的唯一id生成一个独一无二的token&#xff0c;并返回给前端&…...

机器学习深度学习——NLP实战(自然语言推断——微调BERT实现)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——针对序列级和词元级应用微调BERT &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文…...

如何在windows下使用masm和link对汇编文件进行编译

前言 32位系统带有debug程序&#xff0c;可以进行汇编语言和exe的调试。但真正的汇编编程是“编辑汇编程序文件(.asm)->编译生成obj文件->链接生成exe文件”。下面&#xff0c;我就来说一下如何在windows下使用masm调试&#xff0c;使用link链接。 1、下载相应软件 下载…...

Golang字符串基本处理方法

Golang的字符串处理 字符串拼接 两种方法&#xff1a;strings.Join方法和’方法 package mainimport ("fmt""strings" )func main() {num : 20strs : make([]string, 0)for i : 0; i < num; i {strs append(strs, "fht")}//string.join拼…...

算法训练营第三十九天(8.30)| 动态规划Part09:购买股票

Leecode 123.买卖股票的最佳时机 III 123.买卖股票的最佳时机III 123.买卖股票的最佳时机III 题目地址&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目类型&#xff1a;股票问题 class Solution { public:int maxProfit(vector<…...

renren-fast-vue环境升级后,运行正常打包后,访问页面空白

网上各种环境&#xff0c;路径都找了一遍&#xff0c;也没成功。后来发现升级后打包的dist文件结构发生了变化&#xff0c; 1.最开始正常版本是这样 2.升级后是这样&#xff0c;少了日期文件夹 3.问题&#xff1a;打包后的index.html中引入的是config文件夹&#xff0c;而打…...

Uniapp笔记(三)uniapp语法2

一、本节项目预备知识 1、组件生命周期 1.1、什么是生命周期 生命周期(Life Cycle)是指一个对象从创建-->运行-->销毁的整个阶段&#xff0c;强调的是一个时间段 我们可以把每个uniapp应用运行的过程&#xff0c;也概括为生命周期 小程序的启动&#xff0c;表示生命周…...

windows【ftp-FTP】添加配置流程【iis服务】

第一步&#xff1a;自己安装iis服务和ftp服务【自己百度搜索】 第二步&#xff1a;添加ftp站点【配置主动端口默认为21】 第三方配置&#xff1a;ftp被动端口【这里设置为3000-4000】请在防火墙开放此端口【如果是阿里云请在阿里云的后天也开通此端口】【护卫神一般使用55000…...

mysql视图的创建和选项配置详解

在 MySQL 中&#xff0c;可以使用 CREATE VIEW 语句来创建视图。基本的语法如下&#xff1a; CREATE[OR REPLACE][ALGORITHM {UNDEFINED | MERGE | TEMPTABLE}][DEFINER {user | CURRENT_USER}][SQL SECURITY { DEFINER | INVOKER }]VIEW view_name [(column_list)]AS selec…...

Python正则表达式中re.sub自定义替换方法正确使用方法

大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 在使用正则替换时&#xff0c;有时候需要将匹配的结果做对应处理&#xff0c;便可以使用自定义替换方法。 re.sub的用法为&…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南&#xff1a;计算机基础与源码原理深度解析 第一轮提问&#xff1a;基础概念问题 1. 请解释什么是进程和线程的区别&#xff1f; 面试官&#xff1a;进程是程序的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff1b;而线程是进程中的…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

多元隐函数 偏导公式

我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式&#xff0c;给定一个隐函数关系&#xff1a; F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 &#x1f9e0; 目标&#xff1a; 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z​、 …...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”&#xff1a;输入模块&#xff08;GPIO、温度、V_REFINT&#xff09;1.4.2 信号 “调度站”&#xff1a;多路开关1.4.3 信号 “加工厂”&#xff1a;ADC 转换器&#xff08;规则组 注入…...