工做中常見的設計模式-策略模式

前言

最近準備學習下以前項目中用到的設計模式,這裏代碼都只展現核心業務代碼,省略去大多不重要的代碼。算法

代碼大可能是以前一塊兒工做的小夥伴coding出來的,我這裏作一個學習和總結,我相信技術能力的提升都是先從模仿開始的,學習別人的代碼及設計思想也是一種提高的方式。數據庫

 

後續還會有觀察者模式、責任鏈模式的博客產出,都是工做中正式運用到的場景輸出,但願對看文章的你也有啓發和幫助。設計模式

1、業務需求

 
我以前作過在線問診的需求,業務複雜,不少節點須要出發消息推送,好比用戶下單 須要給醫生推送短信和push、醫生接診 須要給用戶發送短信、push、微信等。產品說後期會有不少不一樣的節點觸發消息發送。
 
這裏就開始抽象需求,首先是發送消息,不少消息是一樣的策略,只是組裝的數據是動態拼接的,因此抽象出:buildSms()、buildPush()、buildWechat() 等構造消息體的方法,對於拼接字段相同的都採用同一策略,列入消息A、B須要經過醫生id拼接消息,消息C、D須要經過用戶id拼接消息,那麼A、B就採用同一策略,C、D採用另外一策略。
 
流程圖大體以下:
 
各個業務系統 根據策略構造本身的消息體,而後經過kafka發送個底層服務,進行消息統一推送。
 
 

2、策略模式

 
策略模式(Strategy Pattern)指的是對象具有某個行爲,可是在不一樣的場景中,該行爲有不一樣的實現算法。好比一我的的交稅比率與他的工資有關,不一樣的工資水平對應不一樣的稅率。
策略模式 使用的就是面向對象的繼承和多態機制,從而實現同一行爲在不一樣場景下具有不一樣實現。
策略模式本質:分離算法,選擇實現
 
主要解決在有多重算法類似的狀況下,使用if...else 或者switch...case所帶來的的複雜性和臃腫性。
 
 
代碼示例:
 1 class Client {
 2     public static void main(String[] args) {
 3         ICalculator calculator = new Add();
 4         Context context = new Context(calculator);
 5         int result = context.calc(1,2);
 6         System.out.println(result);
 7     }
 8  
 9  
10     interface ICalculator {
11         int calc(int a, int b);
12     }
13  
14  
15     static class Add implements ICalculator {
16         @Override
17         public int calc(int a, int b) {
18             return a + b;
19         }
20     }
21  
22  
23     static class Sub implements ICalculator {
24         @Override
25         public int calc(int a, int b) {
26             return a - b;
27         }
28     }
29  
30  
31     static class Multi implements ICalculator {
32         @Override
33         public int calc(int a, int b) {
34             return a * b;
35         }
36     }
37  
38  
39     static class Divide implements ICalculator {
40         @Override
41         public int calc(int a, int b) {
42             return a / b;
43         }
44     }
45  
46  
47     static class Context {
48         private ICalculator mCalculator;
49  
50  
51         public Context(ICalculator calculator) {
52             this.mCalculator = calculator;
53         }
54  
55  
56         public int calc(int a, int b) {
57             return this.mCalculator.calc(a, b);
58         }
59     }}
 

3、工做中實際代碼演示

 
爲了代碼簡潔和易懂,這裏用的都是核心代碼片斷,主要看策略使用的方式以及思想便可。
 

一、消息枚舉類,這裏由於消息出發節點衆多,因此每個節點都會對應一個枚舉類,枚舉中包含短信、push、微信、私信等內容。

 1 @Getter
 2 public enum MsgCollectEnum {
 3  
 4     /**
 5      * 枚舉入口:用戶首次提問 給醫生 文案內容(醫生id拼鏈接)
 6      */
 7     FIRST_QUESTION_CONTENT(2101, 1, MsgSmsEnum.SMS_FIRST_QUESTION_CONTENT, MsgPushEnum.PUSH_FIRST_QUESTION_CONTENT, MsgWechatEnum.WECHAT_FIRST_QUESTION_CONTENT);
 8  
 9  
10    /**
11      * 短信文案:用戶首次提問 給醫生 文案內容
12      */
13     SMS_FIRST_QUESTION_CONTENT(STTurnLinkEnum.DOCTOR_QUESTION_SETTING_PAGE.getStoapp(), "您好,有一位用戶向您發起諮詢,請確認接單,趕快進入APP查看吧!{0}");
14  
15  
16     /**
17      * Push文案:用戶首次提問 給醫生 文案內容
18      */
19     PUSH_FIRST_QUESTION_CONTENT(STTurnLinkEnum.DOCTOR_QUESTION_SETTING_PAGE.getStoapp(), STPushAudioEnum.PAY_SUCCESS.getType(), "您好, 有一位用戶向您發起了諮詢服務");
20  
21  
22     ......
23 }

 

 

2,消息節點觸發代碼

這裏是構造上下文MsgContext,主要策略分發的邏輯在最後一行,這裏也會做爲重點來說解
 
1 MsgContext msgContext = new MsgContext();
2 msgContext.setDoctorId(questionDO.getDoctorId());
3 msgContext.setReceiveUid(questionDO.getDrUid());
4 msgContext.setMsgType(MsgCollectEnum.FIRST_QUESTION_CONTENT.getType());
5 this.stContextStrategyFactory.doStrategy(String.valueOf(msgContext.getMsgType()), QuestionMsgStrategy.class).handleSeniority(msgContext);

 

3,策略分發

首先,經過QuestionMsgStrategy.class 找到對應全部的beanMap,而後經過自定義註解找到全部對應策略類,最後經過msgType找到指定的實現類。接着咱們看下策略實現類
 
 1 @Slf4j
 2 public class STContextStrategyFactory {
 3     public <O extends STIContext, T extends STIContextStrategy<O>> STIContextStrategy<O> doStrategy(String type, Class<T> clazz) {
 4         Map<String, T> beanMap = STSpringBeanUtils.getBeanMap(clazz);
 5         if (MapUtils.isEmpty(beanMap)) {
 6             log.error("獲取class:{} 爲空", clazz.getName());
 7         }
 8         try {
 9             for (Map.Entry<String, T> entry : beanMap.entrySet()) {
10                 Object real = STAopTargetUtils.getTarget(entry.getValue());
11                 STStrategyAnnotation annotation = real.getClass().getAnnotation(STStrategyAnnotation.class);
12                 List<String> keySet = Splitter.on("-").omitEmptyStrings().trimResults().splitToList(annotation.type());
13                 if (keySet.contains(type)) {
14                     return entry.getValue();
15                 }
16             }
17         } catch (Exception e) {
18             log.error("獲取目標代理對象失敗:{}", e);
19         }
20         log.error("strategy type = {} handle is null", type);
21         return null;
22     }
23 }
 
 

4,策略實現類

經過自定義註解,而後解析msgType值找到指定策略類,經過不一樣的策略類構造的msg 發送給kafka。
 
 1 @Component
 2 @STStrategyAnnotation(type = "2101-2104-2113-2016", description = "發給醫生,無其餘附屬信息")
 3 public class QuestionMsgSimpleToDoctorStrategyImpl extends AbstractQuestionSendMsgStrategy {
 4  
 5  
 6     @Autowired
 7     private RemoteMsgService remoteMsgService;
 8     @Autowired
 9     private QuestionDetailService questionDetailService;
10  
11  
12     @Override
13     public StarSmsIn buildSmsIn(MsgContext context) {
14         // do something
15     }
16  
17  
18     @Override
19     public StarPushIn buildPushIn(MsgContext context) {
20         // do something
21     }
22  
23  
24     ......
25  
26  
27 }
28  
29  
30 @Slf4j
31 public abstract class AbstractQuestionSendMsgStrategy implements QuestionMsgStrategy {
32     /**
33      * 構建短信消息
34      *
35      * @param context
36      * @return
37      */
38     public abstract StarSmsIn buildSmsIn(MsgContext context);
39  
40  
41     /**
42      * 構建push消息
43      *
44      * @param context
45      * @return
46      */
47     public abstract StarPushIn buildPushIn(MsgContext context);
48  
49  
50     /**
51      * 構建微信公衆號
52      *
53      * @param context
54      * @return
55      */
56     public abstract StarWeChatIn buildWeChatIn(MsgContext context);
57  
58  
59      @Override
60     public STResultInfo handleSeniority(MsgContext msgContext) {
61         // buildMsg and send kafka
62     }
63 }
 
 

四,策略模式缺點

整個消息系統的設計起初是基於此策略模式來實現的,可是在後續迭代開發中會發現愈來愈很差維護,主要缺點以下:
a、接入消息推送的研發同窗須要瞭解每一個策略類,對於相同的策略進行復用
b、節點愈來愈多,策略類也愈來愈多,系統不易維護
c、觸發節點枚舉類散落在各個業務系統中,常常會有相同的節點而不一樣的msgType
 
針對於上述的缺點,又重構了一把消息系統,這次是徹底採用節點配置化方案,提供一個可視化頁面進行配置,將要構造的消息體經過配置寫入到數據庫中,代碼中經過不一樣的佔位符進行數據動態替換。
這裏就再也不展現新版系統的代碼了,重構後 接入方只須要構造msgContext便可,不再須要本身手動去寫不一樣的策略類了。
相關文章
相關標籤/搜索