策略模式-短信模板業(yè)務(wù)場(chǎng)景

前言

最近在開發(fā)公司的短信模板功能仇让,簡(jiǎn)單的說,就是創(chuàng)建一些包含占位符的短信模板躺翻,在發(fā)送短信時(shí)將這些占位符使用特定值替換后再發(fā)出丧叽,例如短信模板中的公司名稱占位符是{companyName},在發(fā)送時(shí)公你,使用具體的公司名稱將{companyName}替換踊淳。
短信模板是一個(gè)獨(dú)立的服務(wù),其他模塊在調(diào)用短信發(fā)送接口時(shí)省店,需要指定短信模板code以及要對(duì)占位符進(jìn)行替換的占位符參數(shù)嚣崭;因?yàn)檎{(diào)用短信發(fā)送的業(yè)務(wù)場(chǎng)景比較多笨触,如果某次調(diào)用傳入的占位符替換參數(shù)與對(duì)應(yīng)短信模板占位符不匹配懦傍,會(huì)導(dǎo)致發(fā)出的短信還包含有未替換的占位符辜王,影響到短信發(fā)送的有效性绝页。因此忘朝,需要在發(fā)送短信時(shí)根據(jù)模板校驗(yàn)傳入的占位符替換參數(shù)减细。
目前定下來的需求是短信模板與傳入的占位符替換參數(shù)必須完全對(duì)應(yīng)才能發(fā)送短信儡嘶,最簡(jiǎn)單的方法就是在發(fā)送短信時(shí)加上判斷御蒲,如果不滿足條件則拒絕發(fā)送纵顾,但是考慮到后續(xù)的拓展性(例如按照業(yè)務(wù)場(chǎng)景設(shè)定不同的拒絕策略)敬鬓,這一個(gè)判斷過程最好是使用策略模式實(shí)現(xiàn)串慰。

策略模式

在閻宏博士的《JAVA與模式》一書中開頭是這樣描述策略(Strategy)模式的:策略模式屬于對(duì)象的行為模式偏塞。其用意是針對(duì)一組算法,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類中邦鲫,從而使得它們可以相互替換灸叼。策略模式使得算法可以在不影響到客戶端的情況下發(fā)生變化
對(duì)于從事JAVA開發(fā)的CRUD工程師們而言庆捺,實(shí)際項(xiàng)目開發(fā)中更多都是寫業(yè)務(wù)邏輯古今,算法可以泛化成各種不同的業(yè)務(wù)場(chǎng)景,在同一個(gè)業(yè)務(wù)場(chǎng)景里滔以,根據(jù)條件的不同需要提供多種不同的業(yè)務(wù)處理邏輯捉腥,這些業(yè)務(wù)處理邏輯的增加或減少是客戶端無需關(guān)注的。

業(yè)務(wù)代碼

本文主要是介紹策略模式你画,重點(diǎn)就只在于短信發(fā)送時(shí)拒絕策略邏輯的處理抵碟,不相關(guān)的代碼就不介紹了桃漾。

UML類圖.png

主要的接口有兩個(gè) SmsTemplatePlaceHolderHandler 短信模板占位符處理器接口,SmsSendRejectStrategy短信發(fā)送拒絕策略接口立磁,SmsTemplatePlaceHolderHandler有一個(gè)默認(rèn)的實(shí)現(xiàn)類DefaultSmsTemplatePlaceHolderHandler呈队,其關(guān)聯(lián)了一個(gè)SmsSendRejectStrategy實(shí)例,在發(fā)送短信時(shí)唱歧,具體的短信發(fā)送拒絕策略實(shí)現(xiàn)類將進(jìn)行具體的發(fā)送拒絕邏輯的處理宪摧,如果允許發(fā)送,則由DefaultSmsTemplatePlaceHolderHandler將替換了占位符的短信模板內(nèi)容發(fā)出颅崩。
其中几于,DefaultSmsTemplatePlaceHolderHandlerSmsSendRejectStrategy的關(guān)系就是一個(gè)具體的策略模式的體現(xiàn),DefaultSmsTemplatePlaceHolderHandler無需關(guān)注拒絕發(fā)送的處理邏輯沿后,調(diào)用SmsSendRejectStrategy實(shí)現(xiàn)類的實(shí)例進(jìn)行處理即可沿彭。

DefaultSmsTemplatePlaceHolderHandler
package com.cube.share.sms.handler;

import com.cube.share.base.utils.JacksonUtils;
import com.cube.share.base.utils.PlaceHolderUtils;
import com.cube.share.sms.constant.SmsConstant;
import com.cube.share.sms.model.param.SmsPlaceHolderParameter;
import com.cube.share.sms.strategy.SmsSendRejectStrategy;
import com.cube.share.sms.strategy.SmsTemplateContext;

/**
 * @author cube.li
 * @date 2021/9/4 12:27
 * @description 默認(rèn)的短信模板占位符處理器
 */
public class DefaultSmsTemplatePlaceHolderHandler implements SmsTemplatePlaceHolderHandler {

    private SmsSendRejectStrategy rejectStrategy;

    public DefaultSmsTemplatePlaceHolderHandler(SmsSendRejectStrategy rejectStrategy) {
        this.rejectStrategy = rejectStrategy;
    }

    @Override
    public String handle(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) {
        //發(fā)送拒絕策略
        rejectStrategy.reject(templateContext, parameter);
        return PlaceHolderUtils.replacePlaceHolder(templateContext.getTemplateContent(),
                JacksonUtils.toMap(parameter),
                SmsConstant.DEFAULT_PLACE_HOLDER_REGEX,
                SmsConstant.DEFAULT_PLACE_HOLDER_KEY_REGEX);
    }
}
SmsSendRejectStrategy
package com.cube.share.sms.strategy;

import com.cube.share.base.utils.JacksonUtils;
import com.cube.share.sms.model.param.SmsPlaceHolderParameter;
import org.springframework.lang.NonNull;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author cube.li
 * @date 2021/9/4 9:49
 * @description 短信發(fā)送的拒絕策略
 */
public interface SmsSendRejectStrategy {

    /**
     * 判斷是否拒絕發(fā)送短信
     *
     * @param templateContext 短信模板上下文
     * @param parameter       填充占位符的參數(shù)
     */
    void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter);

    /**
     * 獲取短信發(fā)送占位符替換參數(shù)Set(不包含value為null)
     *
     * @param parameter 填充占位符的參數(shù)
     * @return Set
     */
    @NonNull
    default Set<String> getParameterSet(SmsPlaceHolderParameter parameter) {
        Map<String, Object> parameterMap = getParameterMap(parameter);
        return parameterMap.keySet();
    }

    /**
     * 獲取短信發(fā)送占位符替換參數(shù)Map(不包含value為null)
     *
     * @param parameter 填充占位符的參數(shù)
     * @return Map
     */
    @NonNull
    default Map<String, Object> getParameterMap(SmsPlaceHolderParameter parameter) {
        Map<String, Object> parameterMap = JacksonUtils.toMap(parameter);
        Map<String, Object> filteredParameterMap = new HashMap<>(4);
        if (parameterMap != null) {
            Set<Map.Entry<String, Object>> entrySet = parameterMap.entrySet();
            entrySet.forEach(stringObjectEntry -> {
                if (stringObjectEntry.getValue() != null) {
                    filteredParameterMap.put(stringObjectEntry.getKey(), stringObjectEntry.getValue());
                }
            });
        }
        return filteredParameterMap;
    }

}

三種拒絕策略的實(shí)現(xiàn)類

package com.cube.share.sms.strategy;

import com.cube.share.sms.model.param.SmsPlaceHolderParameter;

/**
 * @author cube.li
 * @date 2021/9/4 11:54
 * @description 短信發(fā)送拒絕策略-忽略策略,無論短信發(fā)送入?yún)⑴c模板是否匹配,都允許發(fā)送
 */
public class SmsSendIgnoreStrategy implements SmsSendRejectStrategy {

    @Override
    public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) {
        //do nothing
    }
}

package com.cube.share.sms.strategy;

import com.cube.share.base.templates.CustomException;
import com.cube.share.sms.model.param.SmsPlaceHolderParameter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.util.Set;

/**
 * @author cube.li
 * @date 2021/9/4 11:45
 * @description SmsSendAnyMatchStrategy, 只要占位符參數(shù)匹配了短信模板中的任意一個(gè)占位符key,就允許發(fā)送
 */
@Slf4j
public class SmsSendAnyMatchStrategy implements SmsSendRejectStrategy {

    @Override
    public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) {
        Set<String> parameterKeySet = getParameterSet(parameter);
        if (CollectionUtils.intersection(templateContext.getPlaceHolderKeySet(), parameterKeySet).size() <= 0) {
            log.error("短信占位符替換參數(shù)與短信模板完全不匹配,templateContent = {},parameter = {}", templateContext.getTemplateContent(), parameter);
            throw new CustomException("短信占位符替換參數(shù)與短信模板完全不匹配");
        }
    }
}

package com.cube.share.sms.strategy;

import com.cube.share.base.templates.CustomException;
import com.cube.share.sms.model.param.SmsPlaceHolderParameter;
import lombok.extern.slf4j.Slf4j;

import java.util.Set;

/**
 * @author cube.li
 * @date 2021/9/4 11:57
 * @description 短信發(fā)送拒絕策略-完全匹配,只有當(dāng)短信入?yún)⑴c短信模板占位符完全匹配時(shí)才允許發(fā)送
 */
@Slf4j
public class SmsSendTotallyMatchStrategy implements SmsSendRejectStrategy {

    @Override
    public void reject(SmsTemplateContext templateContext, SmsPlaceHolderParameter parameter) {
        Set<String> parameterKeySet = getParameterSet(parameter);
        if (!parameterKeySet.containsAll(templateContext.getPlaceHolderKeySet())) {
            log.error("短信占位符替換參數(shù)與短信模板不完全匹配,templateContent = {},parameter = {}", templateContext.getTemplateContent(), parameter);
            throw new CustomException("短信占位符替換參數(shù)與短信模板不完全匹配");
        }
    }
}
拒絕策略實(shí)例的創(chuàng)建工廠
package com.cube.share.sms.factory;

import com.cube.share.sms.constant.SmsSendRejectStrategyEnum;
import com.cube.share.sms.strategy.SmsSendAnyMatchStrategy;
import com.cube.share.sms.strategy.SmsSendIgnoreStrategy;
import com.cube.share.sms.strategy.SmsSendRejectStrategy;
import com.cube.share.sms.strategy.SmsSendTotallyMatchStrategy;

/**
 * @author cube.li
 * @date 2021/9/4 12:49
 * @description 拒絕策略工廠
 */
public class SmsSendRejectStrategyFactory {

    private static final SmsSendIgnoreStrategy IGNORE_STRATEGY = new SmsSendIgnoreStrategy();

    private static final SmsSendAnyMatchStrategy ANY_MATCH_STRATEGY = new SmsSendAnyMatchStrategy();

    private static final SmsSendTotallyMatchStrategy TOTALLY_MATCH_STRATEGY = new SmsSendTotallyMatchStrategy();

    public static SmsSendRejectStrategy getStrategy(SmsSendRejectStrategyEnum strategyEnum) {
        switch (strategyEnum) {
            case IGNORE:
                return IGNORE_STRATEGY;
            case ANY_MATCH:
                return ANY_MATCH_STRATEGY;
            case TOTALLY_MATCH:
                return TOTALLY_MATCH_STRATEGY;
            default:
                throw new IllegalArgumentException("Illegal StrategyEnum Param");
        }
    }

}
短信發(fā)送服務(wù)
package com.cube.share.sms.service;

import com.cube.share.base.templates.CustomException;
import com.cube.share.sms.config.SmsConfig;
import com.cube.share.sms.constant.SmsSendRejectStrategyEnum;
import com.cube.share.sms.factory.SmsSendRejectStrategyFactory;
import com.cube.share.sms.handler.DefaultSmsTemplatePlaceHolderHandler;
import com.cube.share.sms.handler.SmsTemplatePlaceHolderHandler;
import com.cube.share.sms.model.param.SmsSendParam;
import com.cube.share.sms.strategy.SmsTemplateContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author cube.li
 * @date 2021/9/4 9:03
 * @description 短信服務(wù)
 */
@Service
@Slf4j
public class SmsService {

    @Resource
    private SmsConfig smsConfig;

    private SmsTemplatePlaceHolderHandler placeHolderHandler =
            new DefaultSmsTemplatePlaceHolderHandler(SmsSendRejectStrategyFactory.getStrategy(SmsSendRejectStrategyEnum.ANY_MATCH));

    public void send(SmsSendParam param) {
        String templateContent = smsConfig.getTemplates().get(param.getTemplateCode());
        if (templateContent == null) {
            throw new CustomException("不正確的短信模板");
        }
        SmsTemplateContext templateContext = SmsTemplateContext.from(templateContent, param.getTemplateCode());
        String sendContent = placeHolderHandler.handle(templateContext, param.getParameter());
        log.info("短信發(fā)送: {}", sendContent);
    }
}

測(cè)試

短信模板在配置文件中

#短信
sms:
  #模板
  templates:
    1: "尊敬的用戶您好,{companyName}定于{address}開展主題為{title}的營銷活動(dòng)尖滚,活動(dòng)時(shí)間{startTime}-{endTime}喉刘,歡迎您的光臨!"
    2: "尊敬的用戶您好,{address}開展主題為{title}的營銷活動(dòng)將于明天開始漆弄,歡迎您的光臨!"

單元測(cè)試類

package com.cube.share.sms.service;

import com.cube.share.sms.model.param.SmsPlaceHolderParameter;
import com.cube.share.sms.model.param.SmsSendParam;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

/**
 * @author cube.li
 * @date 2021/9/4 12:00
 * @description
 */
@SpringBootTest
class SmsServiceTest {

    @Resource
    SmsService smsService;

    @Test
    void send() {
        SmsSendParam smsSendParam = new SmsSendParam();
        smsSendParam.setTemplateCode(1);
        SmsPlaceHolderParameter placeHolderParameter = new SmsPlaceHolderParameter();
        placeHolderParameter.setAddress("上海");
        smsSendParam.setParameter(placeHolderParameter);
        smsService.send(smsSendParam);
    }

}

更改拒絕策略睦裳,發(fā)送短信時(shí)日志如下:

  • SmsSendAnyMatchStrategy
2021-09-04 14:34:36.261  INFO 5528 --- [           main] com.cube.share.sms.service.SmsService    : 短信發(fā)送: 尊敬的用戶您好,{companyName}定于上海開展主題為{title}的營銷活動(dòng)撼唾,活動(dòng)時(shí)間{startTime}-{endTime}廉邑,歡迎您的光臨!

可以看出,當(dāng)拒絕策略為SmsSendAnyMatchStrategy時(shí)倒谷,只要占位符入?yún)⑴c短信模板中的占位符有一個(gè)匹配蛛蒙,就能夠發(fā)送成功

  • SmsSendTotallyMatchStrategy
    占位符參數(shù)與模板占位符不完全匹配時(shí)發(fā)送失敗


    不完全匹配.png
2021-09-04 14:38:16.133 ERROR 3896 --- [           main] c.c.s.s.s.SmsSendTotallyMatchStrategy    : 短信占位符替換參數(shù)與短信模板不完全匹配,templateContent = 尊敬的用戶您好,{companyName}定于{address}開展主題為{title}的營銷活動(dòng)渤愁,活動(dòng)時(shí)間{startTime}-{endTime}牵祟,歡迎您的光臨!,parameter = SmsPlaceHolderParameter(companyName=null, title=null, startTime=null, endTime=null, address=上海, url=null)
com.cube.share.base.templates.CustomException: 短信占位符替換參數(shù)與短信模板不完全匹配

    at com.cube.share.sms.strategy.SmsSendTotallyMatchStrategy.reject(SmsSendTotallyMatchStrategy.java:22)

占位符參數(shù)與模板占位符完全匹配時(shí)發(fā)送成功

完全匹配.png

代碼示例:https://gitee.com/li-cube/share/tree/master/sms

總結(jié)

業(yè)務(wù)邏輯說到底就是if-else,使用設(shè)計(jì)模式能夠使代碼更易維護(hù)抖格、更易拓展诺苹,并且代碼的閱讀性更強(qiáng);雖然不使用設(shè)計(jì)模式照樣能夠?qū)崿F(xiàn)業(yè)務(wù)他挎,不過就是多套幾層if-else而已筝尾,但是人活著總歸要有點(diǎn)追求,只有做到不止于業(yè)務(wù)办桨、不止于代碼筹淫,才能成為一個(gè)脫離低級(jí)CRUD的程序員。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市损姜,隨后出現(xiàn)的幾起案子饰剥,更是在濱河造成了極大的恐慌,老刑警劉巖摧阅,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汰蓉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棒卷,警方通過查閱死者的電腦和手機(jī)顾孽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來比规,“玉大人若厚,你說我怎么就攤上這事⊙咽玻” “怎么了测秸?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)灾常。 經(jīng)常有香客問我霎冯,道長(zhǎng),這世上最難降的妖魔是什么钞瀑? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任沈撞,我火速辦了婚禮,結(jié)果婚禮上仔戈,老公的妹妹穿的比我還像新娘关串。我一直安慰自己拧廊,他們只是感情好监徘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吧碾,像睡著了一般凰盔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倦春,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天户敬,我揣著相機(jī)與錄音,去河邊找鬼睁本。 笑死尿庐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呢堰。 我是一名探鬼主播抄瑟,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼枉疼!你這毒婦竟也來了皮假?” 一聲冷哼從身側(cè)響起鞋拟,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惹资,沒想到半個(gè)月后贺纲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪测,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年猴誊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侮措。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稠肘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萝毛,到底是詐尸還是另有隱情项阴,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布笆包,位于F島的核電站环揽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏庵佣。R本人自食惡果不足惜歉胶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巴粪。 院中可真熱鬧通今,春花似錦、人聲如沸肛根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽派哲。三九已至臼氨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芭届,已是汗流浹背储矩。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留褂乍,地道東北人持隧。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像逃片,于是被迫代替她去往敵國和親屡拨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容