前言
剛果商城,用戶登錄 Or 注冊 發送郵箱驗證碼場景,使用
抽象策略模式
實現
什么是抽象策略模式
抽象策略模式
是一種行為型設計模式,它允許定義一系列算法,將每個算法封裝起來,并使它們可以互相替換。這使得客戶端代碼可以獨立于具體的算法實現而變化。
該模式主要包含三個角色。
三個角色
- 策略接口(Strategy Interface): 定義了一組算法的接口,具體的策略類實現這個接口,以便可以在上下文中互相替換。
- 具體策略類(Concrete Strategies): 實現了策略接口的具體算法。
- 上下文(Context): 包含一個對策略接口的引用,可以在運行時切換不同的策略。上下文通常包含一個方法,該方法使用策略接口調用具體的算法。
類圖
首先
策略執行抽象接口(策略接口)
/*** 策略執行抽象*/
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標識(子類自行實現)】@Overridepublic 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;@Resourceprivate MessageSendRemoteService messageSendRemoteService;@Resourceprivate 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
抽象策略接口
登錄策略
@Component
public class MailLoginVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {@Overridepublic String mark() {return "customer_user_login_verify_mail";}// 直接調用父抽象類中方法即可 【核心代碼】@Overridepublic void execute(UserVerifyCodeCommand requestParam) {mailVerifySend(requestParam);}@Overrideprotected String getCachePrefixKey() {return CacheConstant.LOGIN_USER_VERIFY_CODE;}
}
注冊策略
@Component
@RequiredArgsConstructor
public class MailRegisterVerifyCommandHandler extends AbstractMailVerifySender implements AbstractExecuteStrategy<UserVerifyCodeCommand, Void> {@Overridepublic String mark() {return "customer_user_register_mail";}@Overridepublic void execute(UserVerifyCodeCommand requestParam) {mailVerifySend(requestParam);}@Overrideprotected String getCachePrefixKey() {return CacheConstant.REGISTER_USER_VERIFY_CODE;}
}
登錄注冊邏輯僅需各自定義mark標識以及對應存儲redis驗證碼的key前綴即可,代碼簡潔清爽、十分優雅。
接口調用
入參:
@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對應上),根據策略上下文獲取對應策略執行邏輯即可。
策略選擇執行
@Overridepublic 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);}