解锁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.测试各种类型传值
相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
种树人202408192 小时前
如何在 Spring Boot 中启用定时任务
spring boot
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟3 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq