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

委派模式

文章目录

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.自定义注解

java 复制代码
import java.lang.annotation.*;

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

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

3.2.定义抽象委派接口

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

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

3.3.定义具体执行者

java 复制代码
/**
 * 财务部门委派类
 * @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.定义委派者(统一管理委派任务)

java 复制代码
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.定义委派者管理类

java 复制代码
/**
 * 将 委派模式管理类 注入到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层

java 复制代码
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.测试各种类型传值
相关推荐
DuelCode22 分钟前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社226 分钟前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术29 分钟前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理33 分钟前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
猴哥源码34 分钟前
基于Java+springboot 的车险理赔信息管理系统
java·spring boot
YuTaoShao1 小时前
【LeetCode 热题 100】48. 旋转图像——转置+水平翻转
java·算法·leetcode·职场和发展
ai小鬼头2 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
Dcs2 小时前
超强推理不止“大”——手把手教你部署 Mistral Small 3.2 24B 大模型
java
简佐义的博客2 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
东阳马生架构2 小时前
订单初版—1.分布式订单系统的简要设计文档
java