狀態模式使用的一些思考,以及策略模式
狀態模式背景
一般來說,使用狀態模式都是有很多的狀態轉換,如果在程式碼中直接轉換,狀態特別多的情況下回特別的凌亂,不太方便維護也是不太好理解!從設計模式的使用原則來說,一個類的職責最好單一,各種狀態轉換搞得雲裡霧裡的,PD給你增加一個新的需求估計你就要炸了。
筆者需求遇到了啥事
需求簡單的就是當簡訊傳送失敗了,我們要根據第三方提供的能力,通過身份證號碼,姓名,之前使用過的手機號碼獲取到他可能現在還在使用的號碼,然後再次的繼續傳送簡訊。這裡發現了沒有,這個是狀態之間轉換先兆。具體的步驟就是 1、根據條件資訊傳送處理請求 2、過一段時間根據API標識獲取結果(可能是直接給你電話資訊,或者唯一標識能夠通過標識傳送簡訊) 3、然後就是成功還是失敗啦。 如果可以在一個執行緒中直接處理完成,責任鏈模式或者是一個不錯的選擇哦!但是請求第三方的資料都是非同步處理的,沒有辦法一口氣完成啊!
之前的實現的邏輯為:(每個定時器都是根據某種特定的狀態進行獲取相應的資料) 1、(失敗定時任務)定時任務掃描傳送失敗的資料,然後進行傳送處理請求 2、(獲取結果定時器)定時任務獲取之前傳送過處理請求的結果資訊的獲取 3、(傳送簡訊息)定時任務,根據上一個任務處理完成的狀態獲取結果,然後進行傳送資訊; 看上去能夠完成任務,簡單的一個API的介面提供商感覺使用起來還是可以的!現在的需求就是增加一個介面提供商,且介面提供商的處理邏輯還有些不一樣,之前的是能夠直接獲取真實的號碼,新的介面提供商沒有真實的號碼,只給你中間標識資訊,能夠通過標識處理資料。
新需求的一些思考
1、肯定不會在將所有的狀態的轉換放置在不同的定時器中處理,新的員工來了分佈在不同的地方不方便熟悉業務以及理解業務(反正我是看了一天左右才懂的) 2、狀態的轉換的處理一定要集中處理,通過狀態模式進行抽象,方便擴充套件新的狀態(比如有個人的號碼經常傳送失敗,但是反查後又傳送成功了,我們可以在處理之前新增一個狀態,能夠擴充套件之前傳送成功的例子,減少外部API的呼叫,省錢) 3、抽象後的方法處理起來一定是非常的靈活的!能夠在不同的狀態下處理自己應該做的事情,不去幹澀其他狀態下的任務。
設計方案一
狀態轉換的設計流程
簡單的說一下:第一個介面提供商無法滿足業務的要求,也就是最終獲取備案請求成功,不傳送簡訊;或者備案請求成功且傳送簡訊成功,才不用切換介面提供商,否則切換下一個介面提供商
這個方案:狀態比較多,最處理失敗的原因就是和簡訊的傳送耦合在一起了,簡訊傳送的成功失敗可以交給具體的簡訊傳送處理服務去處理相應的邏輯,當接收到後通知處理查詢傳送簡訊失敗的結果服務就好了!這個是兩個服務應該減少耦合性處理。
設計方案二
狀態轉換設計流程
雖然根據第三方的API獲取到具體的聯絡人目前使用的手機資訊之後,傳送簡訊處理邏輯對於整個流程的成功失敗(切換供應商)是有影響的!但是我們應該抽象一下,減少兩個系統之間的耦合,反查身份的流程其實就是獲取到反查的結果就完了,傳送簡訊是下一個能力,且切換介面提供商和當前的服務沒有特別多的聯絡哦!
實現
狀態類實現
狀態轉換Context,根據狀態標識獲取具體的狀態處理類
@Component
public class AssetsStateProcessContext {
public static final Logger logger = LoggerFactory.getLogger(AssetsStateProcessContext.class);
/**
* 所有的狀態的倉庫資訊
*/
@Autowired
private List<AbstractAssetsState> sateRepositorys;
/**
* 空狀態處理器
*/
@Autowired
private AbstractAssetsState assetsDoNothingState;
/**
* 根據當前的反查請求的狀態{@link AssetsRepairRequestDO#getStatus()},獲取當前處理當前狀態的{@link AbstractAssetsState}
* 根據當前的狀態標識,獲取到具體的狀態的處理類
* @param assetsRepairRequestDO
* @return
*/
public AbstractAssetsState getRepairRequestAssetsState(AssetsRepairRequestDO assetsRepairRequestDO) {
AbstractAssetsState resultAssetsState = this.assetsDoNothingState;
if (CollectionUtils.isNotEmpty(this.sateRepositorys) && assetsRepairRequestDO != null
&& StringUtils.isNotEmpty(assetsRepairRequestDO.getStatus())) {
for (AbstractAssetsState assetsState : this.sateRepositorys) {
//獲取處理當前狀態的AssetsState
if (assetsState.isSupport(assetsRepairRequestDO.getStatus())) {
resultAssetsState = assetsState;
break;
}
}
logger.debug("AssetsRepairRequestDO get AbstractAssetsState,id:{},AssetsState:{}",
assetsRepairRequestDO.getId(), resultAssetsState.getStatusEnum().toString());
}
return resultAssetsState;
}
}
定時任務處理
@Component
public class AssetsStateTransitionJob {
private static Logger logger = LoggerFactory.getLogger(AssetsStateTransitionJob.class);
/**
* 失聯資訊處理類
*/
@Autowired
private AssetsRepairRequestBO assetsRepairRequestBO;
@Autowired
private AssetsStateProcessContext assetsStateProcessContext;
/**
* 狀態排程核處理,將查詢需要處理的狀態{@link AssetsRepairRequestStatusEnum}
* 然後執行具體狀態下的處理策略{@link AbstractAssetsState} 具體檢視其實現類
*
* @param status
* @param beginSize
* @param pageSize
*/
public void handlerTimingschedulerAssetsRepairRequest(List<String> status, Integer beginSize, Integer pageSize) {
List<AssetsRepairRequestDO> assetsRepairRequestDOS = assetsRepairRequestBO
.queryRepairRequestByStatusAndEndTimeIsNull(status, beginSize, pageSize);
if (CollectionUtils.isNotEmpty(assetsRepairRequestDOS)) {
for (AssetsRepairRequestDO repairRequestItem : assetsRepairRequestDOS) {
//1、獲取當前資產反查請求對應的狀態處理類
AbstractAssetsState abstractAssetsState = assetsStateProcessContext.getRepairRequestAssetsState(
repairRequestItem);
try {
//2、執行當前狀態下需要處理的業務
abstractAssetsState.processState(repairRequestItem);
} catch (StateTransitionException e) {
//3、當前的狀態與資料庫中的狀態不一致,更新狀態時失敗,進行資料回滾
logger.info("repair request status has changed, rollback,id:{},message:{}",
repairRequestItem.getId(), e.getMessage());
} catch (Exception e) {
logger.error("repair request deal error ,id:{}", repairRequestItem.getId(), e);
}
}
}
}
}
其他傳送簡訊
對於處理不同狀態下不同的介面提供商處理不同的備案邏輯(API介面,是不是返回真實號碼等等),最土的方式就是if else 搞定,或者使用策略模式+工廠處理模式
傳送策略
public abstract class AbstractAssetsSendMessageStrategy {
public AbstractAssetsSendMessageStrategy() {
initChannle();
}
private AssetsRepairChannelEnum channelEnum = AssetsRepairChannelEnum.EMPTY;
public AssetsRepairChannelEnum getChannelEnum() {
return channelEnum;
}
/**
* 初始化設定當前的channel
*/
public void initChannle(){
}
/**
* 設定當前介面提供商的名稱
* @param channelEnum
*/
protected void setChannelEnum(AssetsRepairChannelEnum channelEnum) {
this.channelEnum = channelEnum;
}
/**
* 是否支援 當前的介面提供商
* @param channelName
* @return
*/
public boolean isSupport(String channelName){
return this.channelEnum.getValue().equals(channelName);
}
/**
* 傳送訊息
* @param assetsRepairResultDO
* @return
*/
public abstract boolean sendAssetsMessage(AssetsRepairResultDO assetsRepairResultDO);
}
策略工廠 ,外部直接呼叫策略工廠就可以處理啦,在新增新的介面提供商也沒有關係啦
/**
* 傳送反查資訊抽象策略工廠
*
* @author wangji
*/
@Component
public class AssetsSendMessageFactory {
public static final Logger logger = LoggerFactory.getLogger(AssetsSendMessageFactory.class);
@Autowired
private List<AbstractAssetsSendMessageStrategy> sendMessageStrategies;
@Autowired
private AbstractAssetsSendMessageStrategy assetsEmptySendMessageStategy;
/**
* 傳送簡訊
*
* @param assetsRepairResultDO
* @return
*/
public boolean sendAssetsMessage(AssetsRepairResultDO assetsRepairResultDO) {
boolean result = false;
if (assetsRepairResultDO != null) {
AbstractAssetsSendMessageStrategy assetsSendMessageStrategy = getAssetsSendMessageStrategy(
assetsRepairResultDO);
result = assetsSendMessageStrategy.sendAssetsMessage(assetsRepairResultDO);
logger.info("send assets message reuslt;resultId:{},result:{}", assetsRepairResultDO.getId(), result);
}
return result;
}
public void batchSendAssetsMessages(List<AssetsRepairResultDO> assetsRepairResultDOS) {
Map<Long, Boolean> resultMap = new HashMap<>(1);
if (assetsRepairResultDOS != null) {
resultMap = new HashMap<>(assetsRepairResultDOS.size());
if (CollectionUtils.isNotEmpty(assetsRepairResultDOS)) {
for (AssetsRepairResultDO assetsRepairResultDO : assetsRepairResultDOS) {
resultMap.put(assetsRepairResultDO.getId(), sendAssetsMessage(assetsRepairResultDO));
}
}
}
logger.info("batchSendAssetsMessages result:{}", resultMap.toString());
}
/**
* 獲取當前傳送資產反查介面提供商的傳送策略
*
* @param assetsRepairResultDO
* @return
*/
private AbstractAssetsSendMessageStrategy getAssetsSendMessageStrategy(AssetsRepairResultDO assetsRepairResultDO) {
AbstractAssetsSendMessageStrategy result = assetsEmptySendMessageStategy;
if (assetsRepairResultDO != null && CollectionUtils.isNotEmpty(this.sendMessageStrategies)
&& StringUtils.isNotEmpty(assetsRepairResultDO.getRepairChannel())) {
for (AbstractAssetsSendMessageStrategy assetsSendMessageStrategy : sendMessageStrategies) {
//找到合適的策略 根據介面提供商的型別
if (assetsSendMessageStrategy.isSupport(assetsRepairResultDO.getRepairChannel())) {
result = assetsSendMessageStrategy;
}
}
logger.debug("get AbstractAssetsSendMessageStrategy ,asseRepairtResult id:{},repairChannle:{},Strategy:{}",
assetsRepairResultDO.getId(), assetsRepairResultDO.getRepairChannel(),
result.getChannelEnum().toString());
}
return result;
}
}