【抽象策略模式】实践

前言

刚果商城,用户登录 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);
    }
相关推荐
卡尔特斯1 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源1 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole1 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫2 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide2 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261352 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源2 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱3 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群3 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心3 小时前
从零开始学Flink:数据源
java·大数据·后端·flink