当前位置: 首页 > 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的用法为&…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…...

FOPLP vs CoWoS

以下是 FOPLP&#xff08;Fan-out panel-level packaging 扇出型面板级封装&#xff09;与 CoWoS&#xff08;Chip on Wafer on Substrate&#xff09;两种先进封装技术的详细对比分析&#xff0c;涵盖技术原理、性能、成本、应用场景及市场趋势等维度&#xff1a; 一、技术原…...