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

hyperf 十五 验证器

官方文档&#xff1a;Hyperf 验证器报错需要配合多语言使用&#xff0c;创建配置自动生成对应的语言文件。 一 安装 composer require hyperf/validation:v2.2.33 composer require hyperf/translation:v2.2.33php bin/hyperf.php vendor:publish hyperf/translation php bi…...

ssh访问远程宿主机的VMWare中NAT模式下的虚拟机

1.虚拟机端配置 1.1设置虚拟机的网络为NAT模式 1.2设置虚拟网络端口映射(NAT) 点击主菜单的编辑-虚拟网络编辑器&#xff1a; 启动如下对话框&#xff0c;选中NAT模式的菜单项&#xff0c;并点击NAT设置&#xff1a; 点击添加&#xff0c;为我们的虚拟机添加一个端口映射。…...

【一等奖方案】大规模金融图数据中异常风险行为模式挖掘赛题「NUFE」解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束&#xff0c;大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;正在陆续释出各赛题获奖队伍的方案思路&#xff0c;欢迎广大数据科学家交流讨论。 本方案为【大规模金融图数据中…...

npm install 报错

npm install 报错 npm install 报错 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: yudao-ui-admin1.8.0-snapshot npm ERR! Found: eslint7.15.0 npm ERR! node_modules/eslint npm ERR! dev eslint&q…...

专业人士使用的3个好用的ChatGPT提示

AI正在席卷世界。从自动化各个领域的任务到几秒钟内协助我们的日常生活&#xff0c;它有着了公众还未理解的巨大价值……除非你是专业人士。 专业人士一般都使用什么提示以及如何使用的&#xff1f;这里是经验丰富的懂ChatGPT的专业人士才知道的3个提示。好用请复制收藏。 为…...

doris系列2: doris分析英国房产数据集

1.准备数据 2.doris建表 CREATE TABLE `uk_price_paid` (`id` varchar(50) NOT NULL,`price` int(20),`date` date...

精准运营,智能决策!解锁天翼物联水利水务感知云

面向智慧水利/水务数字化转型需求&#xff0c;天翼物联基于感知云平台创新能力&#xff0c;提供涵盖水利水务泛协议接入、感知云水利/水务平台、水利/水务感知数据治理、数据看板在内的水利水务感知云服务&#xff0c;构建水利水务感知神经系统新型数字化底座&#xff0c;实现智…...

CleanMyMac最新版4.14Mac清理软件下载安装使用教程

苹果电脑是很多人喜欢使用的一种电脑&#xff0c;它有着优美的外观&#xff0c;流畅的操作系统&#xff0c;丰富的应用程序和高效的性能。但是&#xff0c;随着时间的推移&#xff0c;苹果电脑也会产生一些不必要的文件和数据&#xff0c;这些文件和数据就是我们常说的垃圾。那…...

String.Format方法详解

在Java中&#xff0c;String.format() 方法可以用于将格式化的字符串写入输出字符串中。该方法将根据指定的格式字符串生成一个新的字符串&#xff0c;并使用可选的参数填充格式字符串中的占位符。以下是有关 String.format() 方法的更详细信息&#xff1a; 语法 public stati…...

【Mysql】关联查询1对多处理

关联查询1对多返回 遇见的问题 审批主表&#xff0c;和审批明细表&#xff0c;一张审批对应多张明细数据&#xff0c;每条明细数据的状态是不一样的&#xff0c;现在需要根据明细的状态获取到主单子的状态&#xff0c;状态返回矩阵如下 明细状态返回总状态都是已完成已完成都…...