【抽象策略模式】实践

前言

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

什么是抽象策略模式

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

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

三个角色

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

类图

首先

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

java 复制代码
/**
 * 策略执行抽象
 */
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {

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

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

    }

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

策略选择器(上下文)

java 复制代码
/**
 * 策略选择器
 */
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);
        });
    }
}

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

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

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

java 复制代码
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 抽象策略接口

登录策略

java 复制代码
@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;
    }
}

注册策略

java 复制代码
@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前缀即可,代码简洁清爽、十分优雅。

接口调用

入参:

java 复制代码
@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对应上),根据策略上下文获取对应策略执行逻辑即可。

策略选择执行

java 复制代码
    @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);
    }
相关推荐
树码小子2 分钟前
SpringIoC & DI (1):IOC介绍 & Spring IoC使用 & DI
java·后端·spring
tb_first13 分钟前
万字超详细苍穹外卖学习笔记5
java·数据库·spring boot·笔记·学习·spring
铁蛋AI编程实战14 分钟前
ChatWiki 开源 AI 文档助手搭建教程:多格式文档接入,打造专属知识库机器人
java·人工智能·python·开源
Hx_Ma1617 分钟前
SpringBoot消息转换器扩展fastjson
java·spring boot·spring
Coder_preston18 分钟前
Spring/Spring Boot实战:从入门到项目部署
java·spring boot·spring
山岚的运维笔记23 分钟前
SQL Server笔记 -- 第16章:MERGE
java·笔记·sql·microsoft·sqlserver
Andy Dennis28 分钟前
一文漫谈设计模式之创建型模式(一)
java·开发语言·设计模式
belldeep29 分钟前
Java:Tomcat 9 和 mermaid.min.js 10.9 上传.csv文件实现 Markdown 中 Mermaid 图表的渲染
java·tomcat·mermaid·去除flexmark
AutumnorLiuu37 分钟前
C++并发编程学习(二)—— 线程所有权和管控
java·c++·学习
Demon_Hao37 分钟前
JAVA缓存的使用RedisCache、LocalCache、复合缓存
java·开发语言·缓存