【抽象策略模式】实践

news2024/11/27 14:39:21

前言

刚果商城,用户登录 Or 注册 发送邮箱验证码场景,使用抽象策略模式实现

什么是抽象策略模式

抽象策略模式是一种行为型设计模式,它允许定义一系列算法,将每个算法封装起来,并使它们可以互相替换。这使得客户端代码可以独立于具体的算法实现而变化。

该模式主要包含三个角色。

三个角色

  1. 策略接口(Strategy Interface): 定义了一组算法的接口,具体的策略类实现这个接口,以便可以在上下文中互相替换。
  2. 具体策略类(Concrete Strategies): 实现了策略接口的具体算法。
  3. 上下文(Context): 包含一个对策略接口的引用,可以在运行时切换不同的策略。上下文通常包含一个方法,该方法使用策略接口调用具体的算法。

类图

image-20231208162907587

首先

image-20231208141620007

策略执行抽象接口(策略接口)

/**
 * 策略执行抽象
 */
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {

    /**
     * 执行策略标识
     */
    String mark();

    /**
     * 执行策略
     *
     * @param requestParam 执行策略入参
     */
    default void execute(REQUEST requestParam) {

    }

    /**
     * 执行策略,带返回值
     *
     * @param requestParam 执行策略入参
     * @return 执行策略后返回值
     */
    default RESPONSE executeResp(REQUEST requestParam) {
        return null;
    }
}

策略选择器(上下文)

/**
 * 策略选择器
 */
public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {

    /**
     * 执行策略集合
     */
    private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();

    /**
     * 根据 mark 查询具体策略
     *
     * @param mark 策略标识
     * @return 实际执行策略
     */
    public AbstractExecuteStrategy choose(String mark) {
        return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));
    }

    /**
     * 根据 mark 查询具体策略并执行
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     */
    public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        executeStrategy.execute(requestParam);
    }

    /**
     * 根据 mark 查询具体策略并执行,带返回结果
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     * @param <RESPONSE>   执行策略出参范型
     * @return
     */
    public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        return (RESPONSE) executeStrategy.executeResp(requestParam);
    }

    // 项目初始化时,会执行该方法,将所有策略对象都置于map集合中【key是mark标识(子类自行实现)】
    @Override
    public void onApplicationEvent(ApplicationInitializingEvent event) {
        Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);
        actual.forEach((beanName, bean) -> {
            AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());
            if (beanExist != null) {
                throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));
            }
            abstractExecuteStrategyMap.put(bean.mark(), bean);
        });
    }
}

登录/注册发送邮箱策略(具体策略类)

抽象公共邮箱验证码发送(公共逻辑)

将公共发邮箱的代码抽象为一个类,便于复用

public abstract class AbstractMailVerifySender {

    @Value("${customer.user.register.verify.sender}")
    private String sender;

    @Value("${customer.user.register.verify.template-id}")
    private String templateId;

    @Resource
    private MessageSendRemoteService messageSendRemoteService;

    @Resource
    private DistributedCache distributedCache;

    /**
     * 用户注册验证码超时时间
     */
    private static final long REGISTER_USER_VERIFY_CODE_TIMEOUT = 300000;

    /**
     * 获取缓存前缀 Key
     */
    protected abstract String getCachePrefixKey();

    /**
     * 邮箱验证发送
     */
    public void mailVerifySend(UserVerifyCodeCommand requestParam) {
        String verifyCode = RandomUtil.randomNumbers(6);
        // 模板方法模式: 验证码放入缓存,并设置超时时间
        distributedCache.put(CacheUtil.buildKey(getCachePrefixKey(), requestParam.getReceiver()), verifyCode, REGISTER_USER_VERIFY_CODE_TIMEOUT);
        MailSendRemoteCommand remoteCommand = new MailSendRemoteCommand();
        remoteCommand.setTitle("刚果商城邮箱验证码提醒")
                .setReceiver(requestParam.getReceiver())
                .setSender(sender)
                .setTemplateId(templateId)
                .setParamList(Lists.newArrayList(verifyCode));
        messageSendRemoteService.mailMessageSend(remoteCommand);
    }
}

登录注册策略分别继承 AbstractMailVerifySender 抽象类和实现 AbstractExecuteStrategy 抽象策略接口

image-20231208140223756

image-20231208140257567

登录策略

@Component
public class MailLoginVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {
    
    @Override
    public String mark() {
        return "customer_user_login_verify_mail";
    }
    
    // 直接调用父抽象类中方法即可 【核心代码】
    @Override
    public void execute(UserVerifyCodeCommand requestParam) {
        mailVerifySend(requestParam);
    }
    
    @Override
    protected String getCachePrefixKey() {
        return CacheConstant.LOGIN_USER_VERIFY_CODE;
    }
}

注册策略

@Component
@RequiredArgsConstructor
public class MailRegisterVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {

    @Override
    public String mark() {
        return "customer_user_register_mail";
    }

    @Override
    public void execute(UserVerifyCodeCommand requestParam) {
        mailVerifySend(requestParam);
    }

    @Override
    protected String getCachePrefixKey() {
        return CacheConstant.REGISTER_USER_VERIFY_CODE;
    }
}

登录注册逻辑仅需各自定义mark标识以及对应存储redis验证码的key前缀即可,代码简洁清爽、十分优雅。

接口调用

image-20231208140740829

入参:

@Data
@ApiModel("用户验证码")
public class UserVerifyCodeCommand {
    
    @ApiModelProperty(value = "验证类型", notes = "登录验证码,注册认证验证码等", example = "customer_user_login_verify")
    private String type;
    
    @ApiModelProperty(value = "验证平台", notes = "手机短信,邮箱,电话等", example = "mail")
    private String platform;
    
    @NotBlank(message = "接收者不能为空")
    @ApiModelProperty(value = "接收者", example = "m7798432@163.com", notes = "实际发送时更改为自己邮箱")
    private String receiver;
}

type + platform 拼接为对应策略mark(与调用策略mark对应上),根据策略上下文获取对应策略执行逻辑即可。

策略选择执行

    @Override
    public void verifyCodeSend(UserVerifyCodeCommand requestParam) {
        String mark = requestParam.getType() + "_" + requestParam.getPlatform();
        // 策略模式: 根据 mark 选择用户登录或者注册逻辑
        abstractStrategyChoose.chooseAndExecute(mark, requestParam);
    }

    public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
 		// 执行策略核心代码
        executeStrategy.execute(requestParam);
    }

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

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

相关文章

消息队列的基本概念以及作用

目录 一、消息队列概述1.1 什么是消息队列1.2 消息队列的作用和优势1.2.1 解耦1.2.2 异步1.2.3 削峰 1.3 引入消息队列带来的问题1.4 典型应用场景 参考资料 一、消息队列概述 1.1 什么是消息队列 消息队列(Message Queue, MQ)是一种用于在应用程序之间或不同组件之间进行异步…

maven学习笔记总结

目录 一、maven简介 二、GAVP属性 三、基于 IDLE 的 Maven 工程创建 1&#xff09;java标准工程&#xff08;Javase&#xff09;的创建 2&#xff09;java企业工程&#xff08;Javaee&#xff09;的创建 a&#xff09;手动创建 b&#xff09;插件方式创建&#xff08;fil…

模电笔记。。。。

模电 2.8 蜂鸣器 按照蜂鸣器驱动方式分为有源蜂鸣器和无源蜂鸣器 有源的有自己的震荡电路&#xff0c;无源的要写代码控制。 里面有个线圈&#xff0c;相当于电感&#xff0c;储能&#xff0c;通直隔交。 蜂鸣器的参数&#xff1a;额定电压&#xff0c;工作电压&#xff0…

vue3递归组件---树形组件

第一种方式&#xff0c;直接自己调用自己 Tree.vue <template><div class"tree"><div v-for"(item, index) in data" :key"item.name">每一层 {{ item.name }}<Tree v-if"item?.children?.length" :dataitem…

IT外包驻场加速企业IT创新

随着科技的快速发展&#xff0c;企业在追求创新和应用IT技术方面面临挑战。IT外包驻场服务成为许多企业的选择&#xff0c;助力企业实现快速、高效的IT项目实施和应用。 IT外包驻场服务的主要目标是帮助企业在IT创新方面取得突破。这种服务模式不仅仅是提供技术支持&#xff0c…

java 之 继承与多态的详细介绍

文章目录 类的继承1. 基本语法2. 继承的特点3. 方法的重写&#xff08;方法的覆盖&#xff09;super 关键字1. 调用父类的构造器2. 访问父类的成员变量3. 调用父类的方法4. 在构造器中调用父类方法封装性以及访问修饰符抽象方法1. 声明抽象类2. 抽象方法3. 继承抽象类4. 抽象类…

继承与派生(2)

1.派生类的权限&#xff1a;派生类的成员函数可以访问基类的public和protected类型的成员&#xff0c;而派生类的对象只能访问public类型的成员 2.创建顺序&#xff08;先创造后析构&#xff09;&#xff1a;基类函数&#xff0c;派生类函数&#xff0c;组合类函数 类的组合按…

12v转48v升压电源芯片:参数、特点及应用领域

12v转48v升压电源芯片&#xff1a;参数、特点及应用领域 随着电子设备的日益普及&#xff0c;电源芯片的需求也在不断增长。今天我们将介绍一款具有广泛应用前景的升压电源芯片——12v转48v升压电源芯片。该芯片具有宽输入电压范围、高效率、固定工作频率等特点&#xff0c;适…

ShardingSphere 使用distinct或者GROUP BY 报类强转的错

ShardingSphere 使用distinct或者GROUP BY 报错 distinct 使用sharding-sphere分表后 对于使用distinct 会报错 类型对象属性转化异常 &#xff0c;但是去掉DISTINCT后 成功运行 无报错 返回结果正常 但是没有了去重 SELECT DISTINCTlog.* FROMwork_log log,work_log_visi…

网络运维与网络安全 学习笔记2023.12.1

网络运维与网络安全 学习笔记 第三十二天 今日目标 ACL原理与类型、基本ACL配置、高级ACL配置 高级ACL之ICMP、高级ACL之telnet ACL原理与类型 项目背景 为了企业的业务安全&#xff0c;要求不同部门对服务器有不同的权限 PC1不能访问Server PC2允许访问Server 允许其他所…

【C语言】网络字节序和主机字节序

网络字节序和主机字节序是计算机中字节的两种排序方式&#xff0c;它们主要用于解决不同计算机之间数据通信的问题。 一、网络字节序 也被称为大端字节序&#xff0c;是一种标准的字节序。在网络通信中&#xff0c;如果两台主机的字节序不同&#xff0c;可能会导致数据解释的二…

使用命令行移除VSAN中故障磁盘

原创作者&#xff1a;运维工程师 谢晋 使用命令行移除VSAN中故障磁盘 前提故障盘移除 前提 客户有套VSAN环境内有一台服务器的磁盘组出现了一块故障的数据盘&#xff0c;但该盘已经处于完全掉线状态&#xff0c;无法进行正常移除。如下图&#xff1a; 如果遇到这种情况&am…

电子眼+无人机构建平安城市视频防控监控方案

电子眼&#xff08;也称为监控摄像机&#xff09;可以通过安装在城市的不同角落&#xff0c;实时监控城市的各个地方。它们可以用于监测交通违法行为、监控公共场所的安全以及实时监测特定区域的活动情况。通过电子眼的应用&#xff0c;可以帮助警方及时发现并响应各类安全事件…

Linux文件系统与基础IO

文章目录 1 C文件接口1.1 fopen1.2 fwrite、fread、rewind、fclose 2 文件系统调用2.1 open2.1.1 参数2&#xff1a;flags2.1.2 参数3&#xff1a;mode2.1.3 返回值——file descriptor 2.2 write2.3 read2.4 close 3 文件的本质3.1 struct file3.2 一个进程如何与多个文件相关…

倒计时模块复习

经典回顾倒计时 倒计时的基本布局介绍。 一个内容区域和一个输入区域&#xff0c;内容区域进行划分 直接使用flex布局会更快一点。 js代码 我们利用一下模块化思想&#xff0c;直接把获得时间这个功能写成一个函数。方便后续的调用 function getTime() {const date new Date…

使用pytorch查看中间层特征矩阵以及卷积核参数

这篇是我对哔哩哔哩up主 霹雳吧啦Wz 的视频的文字版学习笔记 感谢他对知识的分享 1和4是之前讲过的alexnet和resnet模型 2是分析中间层特征矩阵的脚本 3是查看卷积核参数的脚本 1设置预处理方法 和图像训练的时候用的预处理方法保持一致 2实例化模型 3载入之前的模型参数 4载入…

智能优化算法应用:基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.驾驶训练算法4.实验参数设定5.算法结果6.参考…

带大家做一个,易上手的家常辣子鸡

先从冰箱拿出鸡肉解冻 拿小半根葱 去掉最外面一层皮 切成小段 最备好 花椒 干辣椒 准备四五个大料 起锅烧油 这道菜需要放其他菜两到三倍的油 油温上来之后 放入干辣椒和花椒进行翻炒 等它们都烧黑之后捞出来 这样 辣味就留在油里面了 然后 倒入鸡肉 葱段 大料 然后 倒…

从0到1,手把手带你开发截图工具ScreenCap------001实现基本的截图功能

ScreenCap---Version&#xff1a;001 说明 从0到1&#xff0c;手把手带你开发windows端的截屏软件ScreenCap 当前版本&#xff1a;ScreenCap---001 支持全屏截图 支持鼠标拖动截图区域 支持拖拽截图 支持保存全屏截图 支持另存截图到其他位置 GitHub 仓库master下的Scr…

如何排查rpc mount export: RPC: Timed out问题

文章目录 问题描述查看nfs服务是否运行正常如果以上都通过,尝试下面步骤 问题描述 我们将讨论您在 NFS 客户端上看到的 NFS 错误之一的故障排除。在尝试与 NFS 相关的命令时可以看到此错误&#xff0c;如下所示&#xff1a; 通常&#xff0c;当您看到此错误时&#xff0c;您也…