策略模式Spring框架下开发实例

news/2025/2/22 6:25:08

策略类Spring框架下开发实例

先列出策略模式下需要那些类:

策略接口 (Strategy),定义所有策略类必须遵循的行为。

具体策略类(如 ConcreteStrategyAConcreteStrategyB),实现不同的算法或行为。

上下文类 (Context),持有策略实例并根据条件动态选择和创建策略。

辅助类和注解(作为工具辅助构建类工厂,可忽略),为策略模式的实现提供额外的功能或配置。

具体实现

定义策略接口

采用策略模式的情况一般是封装多重if-else/switch下的不同处理逻辑,这些逻辑可以抽象为一个行为(比如封装参数,做前置校验等),将这个行为定义为接口方法

/**
 * 策略接口
 */
public interface ProductTypeHandler {
    //本例中根据不同产品类型执行不同的返回参数封装逻辑
    void assemble(ResponseBO resBO);
}

定义策略类及其处理逻辑

将上文提到的多重if-else/switch中不同情况及其处理逻辑抽象成具体策略类和其内部方法执行逻辑


/**
 * 记录器参数处理类
 */
@Component
//注解使用见3
@ProductTypeAssembleTag(TAG = EnumProductType.PROCESS_RECORDER)
public class RecorderHandler implements ProductTypeHandler {
    @Autowired
    private RecorderService service;
    @Override
    public void assemble(ResponseBO resBO) {
        //记录器的处理逻辑
        int bindDeviceNums = service.getNumByUserId(ContextUtil.getUserId());
        resBO.setValue(bindDeviceNums);
    }
}
@Component
@ProductTypeAssembleTag(TAG = EnumProductType.COMMANDER)
//继承见4
public class CommanderHandler  extends CommonHandler implements ProductTypeHandler {
    @Override
    public void assemble(ResponseBO resBO) {
       //控制器的处理逻辑
       ...
    }
}

这里用到的注解和继承可以先忽视,3,4中会做解释

Context上下文类

这个类的核心就是存储map所有的策略类,然后提供getHandler()方法,供外界匹配获取对应策略类执行相关逻辑

那么这个map是如何存储的呢?我在实践中遇到是通过Map<TypeEnum,Handler>也就是定义枚举类作为键,存储其对应枚举类

此处有一个可拓展的点就是这个map是如何封装的,我遇到的有这么两种:

  1. 在Context中将所有策略类定义为属性,然后@Resourse注入
  2. 通过注解的方式进行解耦

第一种自动注入+switch的方式

@Component
public class ProductTypeHandlerContext {
    @Resource
    private CommonHandler commonHandler;
    @Resource
    privateRecorderHandler recorderHandler;
    //...其他策略类

    public ProductTypeHandler getHandler(EnumProductType emumType) {
        ProductTypeHandler handler = null;
        switch (EnumProductType) {
            case COMMANDER:
                handler = commonHandler;
                break;
            case RECORDER:
                handler = recorderHandler;
                break;
            default:
                throw new BizException("未匹配到目标类型");
                break;
        }
        return handler;
    }
}

可以看到第一种方案比较轻量级,下面来着重举例看看第二种情况:

@Component
public class HandlerContext{
    private static final Map<EnumProductTypeProductTypeHandler> handlerMap = new HashMap<>();

    @Autowired
    private HandlerContext(List<ProductTypeHandler> handlers) {
        handlers.forEach(processor -> {
            //这一段是通过反射获取到策略类
            long count = new ArrayList<>(Arrays.asList(processor.getClass().getInterfaces())).stream().filter(m -> m.getName().equals("org.springframework.aop.framework.Advised")).count();
            try {
                Class<?> ifaceClass = (Class) (count > 0 ? processor.getClass().getMethod("getTargetClass").invoke(processor) : processor.getClass());
                this.handlerMap.put(ifaceClass.getAnnotation(ProductTypeAssembleTag.class).TAG(), processor);
            } catch (Exception e) {
                log.error("HandlerContext初始化失败",e);
                throw new BizException("HandlerContext初始化失败");
            }
        });
    }
    
    public ProductTypeHandler getHandler(EnumProductType emumType) {
        return handlerMap.get(emumType);
    }
}

可以看到Context主要分为三部分:

  1. map
  2. 构造函数–>初始化map
  3. getHandler()

这里的重点在2中的HandlerContext()构造方法,通过@Autowired注解,Spring会将所有继承了策略接口ProductTypeHandler的策略类注入为列表作为参数,那么在方法内部对列表中的策略类依次进行map.put就可以了

那如何将策略类对应到其所属的EnumType上呢?这里通过自定义一个注解,为其添加一个名为Tag的EnumType属性,在每个策略类上面加上这个注解,就大功告成了,这也是上面的策略类的注解来源

/**
 * 注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductTypeAssembleTag {
    EnumsLogin.EnumProductType TAG();
}

至于HandlerContext()中的try中所涉及的逻辑,是检查当前的 processor 类是否是一个 Spring AOP 代理类:

"org.springframework.aop.framework.Advised"接口是 Spring AOP 代理类的标志。如果 count > 0,说明 processor 是一个 AOP 代理类。

如果 processor 是 AOP 代理类,则调用 processor.getClass().getMethod("getTargetClass").invoke(processor) 获取目标类的 Class 对象,这样可以获取到代理类的实际目标类(即被代理的类)。如果 processor 不是代理类,则直接使用 processor.getClass()

getTargetClass() 方法是 AOP 代理类的一个方法,它返回代理对象的实际目标类(即未被代理的原始类)。

进一步思考

如果策略类a,b的执行逻辑是一样的,那么要写重复的代码吗?

这时候就可以回归到最基础的继承上,通过定义一个CommonHandler,在其中写一次默认逻辑,然后其他策略类进行继承这也是上面的策略类有继承的原因

public class CommonHandler {
    public void assemble(ResponseBO resBO) {
       log.info("默认处理--目前没有特殊处理逻辑");
    }
}

至此,一个通过注解和继承优化的策略类实现方案就得以使用,在调用类通过注入HandlerContext,调用getHandler(enumType).assemble就可以传入枚举类获取对应策略类执行对应逻辑

总结

针对Context中map的两种初始化方案:

方案优点缺点
自动注入+switch- 简单直观,易于理解和维护
- 性能较好,避免了反射开销
- 扩展性差,需要修改 switch 语句才能添加新策略
- 高耦合,修改策略类需要修改 Context
注解解耦- 解耦性强,符合开闭原则
- 易于扩展,不需要修改现有代码
- 支持 AOP 代理
- 性能开销较高,使用了反射
– 启动时的反射性能开销

http://www.niftyadmin.cn/n/5861785.html

相关文章

(一)趣学设计模式 之 单例模式!

目录 一、啥是单例模式&#xff1f;二、为什么要用单例模式&#xff1f;三、单例模式怎么实现&#xff1f;1. 饿汉式&#xff1a;先下手为强&#xff01; &#x1f608;2. 懒汉式&#xff1a;用的时候再创建&#xff01; &#x1f634;3. 枚举&#xff1a;最简单最安全的单例&a…

leetcode刷题第十三天——二叉树Ⅲ

本次刷题顺序是按照卡尔的代码随想录中给出的顺序 翻转二叉树 226. 翻转二叉树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*//*总体思路就是&#xff0c;对于每一个结点&…

如何利用 Vue 的生命周期钩子进行初始化和清理操作?

一、初始化操作的核心钩子 1. created&#xff08;选项式API&#xff09; export default {data() {return { user: null };},created() {// 适合初始化数据、发起非DOM操作请求this.fetchUser();},methods: {async fetchUser() {const response await fetch(/api/user);thi…

什么容错性以及Spark Streaming如何保证容错性

一、容错性的定义 容错性是指一个系统在发生故障或崩溃时&#xff0c;能够继续运行并提供一定服务的能力。在网络或系统中&#xff0c;这通常涉及到物理组件损坏或软件失败时系统的持续运行能力。容错系统的关键特性包括负载平衡、集群、冗余、复制和故障转移等。 二、Spark …

win10把c盘docker虚拟硬盘映射迁移到别的磁盘

c盘空间本身就比较小、如果安装了docker服务后&#xff0c;安装的时候没选择其他硬盘&#xff0c;虚拟磁盘也在c盘会占用很大的空间&#xff0c;像我的就三十多个G&#xff0c;把它迁移到其他磁盘一下子节约几十G 1、先输入下面命令查看 docker 状态 wsl -l -v 2、如果没有停止…

oracle怎么创建定时任务

在Oracle中创建定时任务&#xff0c;可以使用DBMS_SCHEDULER包&#xff0c;以下是创建定时任务的详细步骤&#xff1a; 1. 创建作业 需要创建一个作业&#xff0c;用于执行定时任务&#xff0c;作业是一组SQL语句或PL/SQL代码&#xff0c;可以定期执行。 BEGINDBMS_SCHEDULE…

从DeepSeek大爆发看AI革命困局:大模型如何突破算力囚笼与信任危机?

目录 从DeepSeek大爆发看AI革命困局&#xff1a;大模型如何突破算力囚笼与信任危机&#xff1f; 小瓜有话说——为什么想写这篇博文 一、算力军备竞赛下的临时繁荣 1、技术奇点的陷阱 2、行业困境 二、三类企业的生存实验 1. 守旧派的黄昏&#xff1a;参数崇拜者的绝击 …

pycharm将当前项目上传到github

要将当前项目从 PyCharm 上传到 GitHub&#xff0c;你可以按照以下步骤操作&#xff1a; 1. 创建一个 GitHub 仓库 登录到 GitHub。点击右上角的 按钮&#xff0c;然后选择 New repository。填写仓库名称、描述等信息&#xff0c;点击 Create repository。 2. 在 PyCharm 中…