解锁Spring Boot中的设计模式—03.委派模式:探索【委派模式】的奥秘与应用实践!

news2025/3/14 12:20:25

委派模式

文章目录

  • 委派模式
    • 1.简述
      • **应用场景**
      • **优缺点**
      • **业务场景示例**
    • 2.类图
    • 3.具体实现
      • 3.1.自定义注解
      • 3.2.定义抽象委派接口
      • 3.3.定义具体执行者
      • 3.4.定义委派者(统一管理委派任务)
      • 3.5.定义委派者管理类
    • 4.测试
      • 4.1.controller层
      • 4.2.测试不同场景
        • 4.2.1.测试生产部门计算费用
        • 4.2.2.测试财务部门计算费用
        • 4.2.3.测试各种类型传值

1.简述

委派模式是一种负责任务的调度和分配模式,类似于代理模式但更注重结果而非过程。它可以被视为一种特殊情况下的静态代理的全权代理,但并不属于GOF 23种设计模式之一,而是归类为行为型模式。

委派模式在Spring框架中广泛应用,其中最常见的例子是DispatcherServlet,它充分利用了委派模式的特性。

在Spring框架中,DispatcherServlet是一个核心组件,负责接收HTTP请求并将其分发给相应的处理器(Controller)进行处理。DispatcherServlet利用了委派模式的特性来实现请求的转发和处理。

具体来说,DispatcherServlet接收到HTTP请求后,根据请求的信息(如URL、请求方法等),会将请求委派给相应的处理器进行处理。这个委派的过程就是典型的委派模式应用。

在Spring中,DispatcherServlet并不直接处理请求,而是通过HandlerMapping确定请求对应的处理器(Controller),然后委派给对应的Controller进行处理。处理完成后,再由DispatcherServlet负责返回响应给客户端。

这种设计使得Spring的Web应用具有良好的灵活性和可扩展性。开发者可以根据自己的需求定制不同的HandlerMapping和Controller,而DispatcherServlet作为统一的入口,负责请求的委派和响应的处理,使得整个Web应用的架构更加清晰和易于维护。

应用场景

委派模式适用于以下场景:

  • 当需要实现表现层与业务层之间的松耦合时,委派模式能够提供一个良好的解决方案。
  • 在需要编排多个服务之间的调用时,特别是像责任链模式这样的情况下,委派模式尤为适用。
  • 当需要再封装一层服务查找和调用时,委派模式也是一种常见的选择。

优缺点

优点

  • 隐藏实现:对内部实现进行了封装,使得调用方不必关心具体的实现细节,增强了系统的安全性和稳定性。
  • 易于扩展:通过委派模式,可以方便地添加新的服务或功能,而不会影响到已有的代码。
  • 简化调用:调用方只需要知道委派对象,而不需要知道具体的执行细节,降低了系统的复杂度和维护成本。

缺点

  • 膨胀和管理难度:与静态代理相似,随着具体执行类和委派类的扩展,代码容易膨胀,难以管理,需要谨慎设计和维护。

业务场景示例

在关务系统中,费用计算是一个关键功能。不同部门可能采用不同的计算方式。为了应对这种多样性,系统需要根据不同部门的要求,动态选择合适的费用计算算法,以确保费用的准确性和合规性。

在这样的场景下,可以借助委派模式来实现动态选择合适的计算算法。下面,我们以Spring Boot版本的委派模式为例来展示其具体实现。

2.类图

在这里插入图片描述

3.具体实现

3.1.自定义注解

import java.lang.annotation.*;

@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Delegate {

	/**
	 * 执行任务类型
	 */
	String type () default "";
}

3.2.定义抽象委派接口

/**
 * 委派任务通用处理接口
 */
public interface DelegateHandler<R,T> {

	/**
	 * 抽象处理方法
	 * @param t 处理任务参数
	 */
	R process(T t);
}

3.3.定义具体执行者

/**
 * 财务部门委派类
 * @author 13723
 * @version 1.0
 * 2024/1/7 14:52
 */
@Delegate(type = "finance")
@Component
public class FinanceDepartmentDelegate implements DelegateHandler<ResultObject, BigDecimal>{
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	/**
	 * 财务部门处理(将数据加上税费)
	 * @param s 处理任务参数
	 * @return
	 */
	@Override
	public ResultObject process(BigDecimal s) {
		ResultObject instance = ResultObject.createInstance(true);
		BigDecimal res = BigDecimal.ZERO;
		if (s != null) {
			res = s.add(new BigDecimal("300"));
			instance.setData(res);
		}
		instance.setMessage("财务部门处理数据结束!");
		logger.error("财务部门处理数据结束!处理前:{},处理后:{}",s,res);
		return instance;
	}
}






/**
 * 生产部门委派类
 * @author 13723
 * @version 1.0
 * 2024/1/7 14:44
 */
@Delegate(type = "product")
@Component
public class ProductionDepartmentDelegate implements DelegateHandler<ResultObject, BigDecimal> {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());


	/**
	 * 生产部门处理(将数据重新计算)
	 * @param s 处理任务参数
	 * @return
	 */
	@Override
	public ResultObject process(BigDecimal s) {
		ResultObject instance = ResultObject.createInstance(true);
		BigDecimal res = BigDecimal.ZERO;
		if (s != null) {
			res = s.multiply(new BigDecimal("1.6"));
			instance.setData(res);
		}
		instance.setMessage("生产部门处理数据结束!");
		logger.error("生产部门处理数据结束!处理前:{},处理后:{}",s,res);
		return instance;
	}
}

3.4.定义委派者(统一管理委派任务)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;

import java.lang.invoke.MethodHandles;
import java.util.*;

/**
 *  委派角色管理类
 * @author 13723
 * @version 1.0
 * 2024/1/7 14:58
 */
@Delegate
public class DelegateManager extends DelegatePatternConfiguration {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	/**
	 * 存放具体委派角色类型
	 * k-具体委派类型
	 * v-具体任务角色
	 */
	private Map<String, List<DelegateHandler>> handlerMap;

	/**
	 * 存放委派模式中的各种真正执行任务的角色
	 * 分组放
	 * @param iHandlerList
	 */
	public void setHandlerMap(List<DelegateHandler> iHandlerList) {
		handlerMap = scanHandlers(iHandlerList);
	}

	/**
	 * 将具体的委派角色 获取进行统一管理
	 * @param handlerList 具体实现委派的类集合
	 * @return 存放委派
	 */
	private Map<String, List<DelegateHandler>> scanHandlers(List<DelegateHandler> handlerList) {
		ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
		scanner.addIncludeFilter(new AnnotationTypeFilter(Delegate.class));
		Map<String, List<DelegateHandler>> handlerMap = new HashMap<>();
		for (DelegateHandler handler : handlerList) {
			Class<?> targetClass = handler.getClass();
			if (hasDutyAnnotation(targetClass)) {
				String type = getTypeAnnotationValue(targetClass);
				handlerMap.computeIfAbsent(type, key -> new ArrayList<>()).add(handler);
			}
		}
		return handlerMap;
	}
    
	/**
	 * 判断该类是否是 使用使用了Delegate注解
	 * @param clazz 类名称
	 * @return true使用了
	 */
	private boolean hasDutyAnnotation(Class<?> clazz ) {
		return AnnotationUtils.findAnnotation(clazz, Delegate.class) != null;
	}

	/**
	 * 判断使用了的注解@Delegate 是否填写了类型
	 * @param clazz
	 * @return
	 */
	private String getTypeAnnotationValue(Class<?> clazz) {
		Delegate dutyAnnotation = AnnotationUtils.findAnnotation(clazz, Delegate.class);
		if (dutyAnnotation == null) {
			throw new IllegalStateException("Delegate annotation not found for class: " + clazz);
		}
		return dutyAnnotation.type();
	}


	/**
	 * 根据类型 委派具体执行任务的角色
	 * @param type 任务类型
	 * @param t 传递参数
	 * @return 执行后 最终返回结果
	 */
	public <R, T> R process(String type, T t) {
		List<DelegateHandler> handlers = handlerMap.get(type);
		R result = null;
		if (!CollectionUtils.isEmpty(handlers)) {
			// 注意 这里需要考虑多个执行角色问题,遇到多个相同类型执行角色,考虑使用责任链,别再此模式上死磕。
			for (DelegateHandler<R, T> handler : handlers) {
				result = handler.process(t);
			}
		}else {
			throw new RuntimeException("no match delegate class !");
		}
		return result;
	}
}

3.5.定义委派者管理类

/**
 * 将 委派模式管理类 注入到Spring中
 * @author 13723
 * @version 1.0
 * 2024/1/7 15:16
 */
@Configuration
public class DelegatePatternConfiguration {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	@Bean
	public DelegateManager delegateManager(List<DelegateHandler> handlers) {
		// 创建对象 会获取全部使用注解的类,将其注入到对应map集合中
		DelegateManager delegateManager = new DelegateManager();
		delegateManager.setHandlerMap(handlers);
		return delegateManager;
	}
}

4.测试

4.1.controller层

import com.hrfan.java_se_base.config.ResultObject;
import com.hrfan.java_se_base.pattern.decorator_pattern.config.DecorateManager;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.lang.invoke.MethodHandles;
import java.math.BigDecimal;
import java.util.Objects;

/**
 * 委派模式测试类
 * @author 13723
 * @version 1.0
 * 2024/1/7 15:20
 */
@RestController
@RequestMapping("/v1/delegate")
public class DelegateTestController {
	@Resource
	private DelegateService service;
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	@Resource
	DelegateManager manager;

	@PostMapping("/test")
	public ResultObject test(@RequestBody DelegateMoneyParams params){
		ResultObject instance = ResultObject.createInstance(false);
		instance.setMessage("计算失败!");
		Objects.requireNonNull(params);
		if (StringUtils.isNotBlank(params.getType())){
			switch (params.getType()){
				case "a":
					instance =  manager.process("product", params.getTotal());
					return instance;
				case "b":
					instance =  manager.process("finance", params.getTotal());
					return instance;
				default:
					return instance;
			}
		}
		return instance;
	}
}

4.2.测试不同场景

4.2.1.测试生产部门计算费用

在这里插入图片描述

4.2.2.测试财务部门计算费用

在这里插入图片描述

4.2.3.测试各种类型传值

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

人工智能学习与实训笔记(四):神经网络之NLP基础—词向量

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 四、自然语言处理 4.1 词向量 (Word Embedding) 4.1.1 词向量的生成过程 4.1.2 word2vec介绍 4.1.3 word2vec&#xff1a;skip-gram算法的实现 4.2 句向量 - 情感分析 4.2.1 LSTM (Long S…

面向知识图谱的大模型应用

【引子】春节期间&#xff0c;读了论文《An LLM Compiler for Parallel Function Calling》(https://arxiv.org/abs/2312.04511), 颇受启发&#xff0c;或许可以通过LLM Compiler 实现已有知识图谱系统的大模型赋能&#xff0c; 实现面向知识图谱的大模型应用。 知识图谱的应用…

Python一级考试笔记

Python一级考试笔记【源源老师】 前置知识&#xff1a;&#xff08;了解即可&#xff09; Python常见的几种编程环境&#xff1a;IDLE&#xff08;自带&#xff09;、Visual Studio Code、Jupyter、pyCharm&#xff1b; python版本&#xff1a;python3 和 python2&#xff08;…

GitLab配置SSHKey

段落一&#xff1a;什么是SSH密钥 SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于安全地远程登录和执行命令。SSH密钥是一种用于身份验证的加密文件&#xff0c;它允许您在与远程服务器通信时&#xff0c;无需输入密码即可进行认证。在GitLab中配置S…

2023我患上了AI焦虑

2023我患上了AI焦虑 来自&#xff1a;宝玉 原文链接&#xff1a;https://baoyu.io/blog/ai/i-am-suffering-from-ai-anxiety-in-2023 2023 年对我来说是神奇的一年&#xff0c;我意外的从一个程序员变成了一个 AI 资讯届的“网红”&#xff0c;到年底的时候我在 X 平台的阅读量…

面试经典150题【1-10】

文章目录 面试经典150题【1-10】88. 合并两个有序数组27.移除元素26.删除有序数组中的重复项80.删除有序数组中的重复项II169.多数元素189.轮转数组121.买卖股票的最佳时机1122. 买卖股票的最佳时机 II55.跳跃游戏![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ff…

SpringBoot实现OneDrive文件上传

SpringBoot实现OneDrive文件上传 源码 OneDriveUpload: SpringBoot实现OneDrive文件上传 获取accessToken步骤 参考文档&#xff1a;针对 OneDrive API 的 Microsoft 帐户授权 - OneDrive dev center | Microsoft Learn 1.访问Azure创建应用Microsoft Azure&#xff0c;使…

Qt的安装

下载安装包 我们选择Open Source的版本。 可以从https://www.qt.io/download-open-source直接下载最新版本&#xff0c;对于想要选择特定版本&#xff0c;或者尝试beta版功能的用户&#xff0c;也可以从https://download.qt.io选择自己需要的版本。 如果你的网速可以的话&am…

语音唤醒——

文章目录 配置主代码 参考文档&#xff1a;https://picovoice.ai/docs/quick-start/porcupine-python/ 配置 pip install pvporcupine主代码 ACCESS_KEY&#xff1a;需要将该参数填入即可 # # Copyright 2018-2023 Picovoice Inc. # # You may not use this file except in …

前端开发,Vue的双向数据绑定的原理

目录 一、什么是前端 二、Vue.JS框架 三、双向数据绑定 四、Vue的双向数据绑定的原理 一、什么是前端 前端通常指的是网页或应用程序中用户直接交互和感知的部分&#xff0c;也称为客户端。前端开发涉及使用HTML、CSS和JavaScript等技术来构建用户界面和交互功能。前端开发…

个人 AI 的革命:Nvidia‘s Chat with RTX 深度探索

个人 AI 的革命&#xff1a;Nvidias Chat with RTX 深度探索 Nvidia 推出的 Chat with RTX 预示着个人 AI 新时代的到来。2 月 13 日&#xff0c;Nvidia 官宣了自家的 AI 聊天机器人&#xff0c;这不仅是人工智能交互的渐进式改进&#xff1b;更代表了个人如何利用自己的数据进…

ubuntu 22.04.3 live server安装JDK21与远程编程环境

ubuntu 22.04.3 live server安装JDK21与远程编程环境 一、安装jdk21 解压jdk压缩包&#xff0c;命令&#xff1a; tar -zxvf jdk-21_linux-x64_bin.tar.gz打开环境变量&#xff0c;命令&#xff1a; sudo vim /etc/profile配置环境变量 export JAVA_HOME/root/jdk-21.0.2 …

javaweb学习day03(JS+DOM)

一、javascript入门 1 官方文档 地址: https://www.w3school.com.cn/js/index.asp离线文档: W3School 离线手册(2017.03.11 版).chm 2 基本说明 JavaScript 能改变 HTML 内容&#xff0c;能改变 HTML 属性&#xff0c;能改变 HTML 样式 (CSS)&#xff0c;能完成 页面的数据…

18-k8s控制器资源-cronjob控制器

job控制器是执行完一次任务&#xff0c;就结束&#xff1b; cronjob控制器&#xff0c;是基于job控制器&#xff0c;定期频率性执行任务&#xff1b;等同于linux系统中的crontab一样&#xff1b; 1&#xff0c;编辑cronjob资源清单 [rootk8s231 pi]# vim cronjob.yaml apiVers…

Dynamo之视图过滤器介绍

你好&#xff0c;这里是BIM的乐趣&#xff0c;我是九哥~ 今天简单整理一篇&#xff0c;源于最近很多人给我留言&#xff0c;问如何添加过滤器&#xff0c;那么我就简单把视图过滤器这块的节点及其用法整理下&#xff0c;基本上自带的节点就够用了。 获取项目中的所有视图过滤器…

《剑指 Offer》专项突破版 - 面试题 48 : 序列化和反序列化二叉树(C++ 实现)

目录 前言 一、序列化二叉树 二、反序列化二叉树 前言 题目链接&#xff1a;LCR 048. 二叉树的序列化与反序列化 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 请设计一个算法将二叉树序列化成一个字符串&#xff0c;并能将该字符串反序列化出原来的二叉树。…

【c++】const引用

Hello everybody!今天给大家讲讲有关const引用部分的知识&#xff0c;因为这部分知识涉及到const与引用直接如何灵活的运用&#xff0c;且不太好理解。所以我认为讲一下这里的知识还是很有必要的&#xff01; 1.权限可缩小 首先&#xff0c;当我们定义了a&#xff0c;在给a取别…

JAVA面试题并发篇

1. 线程状态 要求 掌握 Java 线程六种状态 掌握 Java 线程状态转换 能理解五种状态与六种状态两种说法的区别 六种状态及转换 分别是 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态 此时未与操作系统底层线程关联 可运行 调用了 start 方法…

Vue2:组件间通信框架Vuex

一、原理图及作用 功能介绍&#xff1a; 简单说就是Vue项目中&#xff0c;各个组件间通信的一种框架 相较于之前的全局事件总线&#xff0c;该框架更实用&#xff01; 提高了代码的复用率&#xff0c;把核心业务代码&#xff0c;集中到store中&#xff0c;这样&#xff0c;一处…

【机器学习笔记】 6 机器学习库Scikit-learn

Scikit-learn概述 Scikit-learn是基于NumPy、 SciPy和 Matplotlib的开源Python机器学习包,它封装了一系列数据预处理、机器学习算法、模型选择等工具,是数据分析师首选的机器学习工具包。 自2007年发布以来&#xff0c;scikit-learn已经成为Python重要的机器学习库了&#xff…