工作中常见的设计模式-策略模式

前言

最近准备学习下之前项目中用到的设计模式,这里代码都只展示核心业务代码,省略去大多不重要的代码。

代码大多是之前一起工作的小伙伴coding出来的,我这里做一个学习和总结,我相信技术能力的提高都是先从模仿开始的,学习别人的代码及设计思想也是一种提升的方式。

后续还会有观察者模式、责任链模式的博客产出,都是工作中正式运用到的场景输出,希望对看文章的你也有启发和帮助。

一、业务需求

我之前做过在线问诊的需求,业务复杂,很多节点需要出发消息推送,比如用户下单 需要给医生推送短信和push、医生接诊 需要给用户发送短信、push、微信等。产品说后期会有很多不同的节点触发消息发送。

这里就开始抽象需求,首先是发送消息,很多消息是同样的策略,只是组装的数据是动态拼接的,所以抽象出:buildSms()、buildPush()、buildWechat() 等构造消息体的方法,对于拼接字段相同的都采用同一策略,列入消息A、B需要通过医生id拼接消息,消息C、D需要通过用户id拼接消息,那么A、B就采用同一策略,C、D采用另一策略。

流程图大致如下:

各个业务系统 根据策略构造自己的消息体,然后通过kafka发送个底层服务,进行消息统一推送。

二、策略模式

策略模式(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     }}

三、工作中实际代码演示

为了代码简洁和易懂,这里用的都是核心代码片段,主要看策略使用的方式以及思想即可。

1、消息枚举类,这里因为消息出发节点众多,所以每一个节点都会对应一个枚举类,枚举中包含短信、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即可,再也不需要自己手动去写不同的策略类了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏算法猿的成长

深度学习的一些经验总结和建议| To do v.s Not To Do

昨天看到几篇不同的文章写关于机器学习的to do & not to do,有些观点赞同,有些不赞同,是现在算法岗位这么热门,已经不像几年前一样,可能跑过一些项目...

10740
来自专栏搜狗测试

智能算法评测系统实践(一)

随着人工智能的发展,我们现在各个产品线中都融入大量的智能算法,方便了用户的同时也给我们评价产品的具体效果带来了很大的困难。这里就简单介绍一些我们在智能算法评测实...

16920
来自专栏Rust语言学习交流

Rust Async: futures-timer源码解析

本文转载自:https://zhuanlan.zhihu.com/p/78036342

22030
来自专栏搜狗测试

一种质量分层的模型以及总结思考

1、功能质量 2009年,我们还在使用PC电脑上淘宝、上人人网。这一时期软件质量要保障的主要是软件功能的可用性。因此,我们把这一阶段的质量称为“功能质量”,质...

16550
来自专栏暴走大数据

Spark Core源码精读计划15 | 心跳接收器HeartbeatReceiver

按照SparkContext初始化的顺序,下一个应该是心跳接收器HeartbeatReceiver。由于笔者感染乙流仍然没有痊愈,状态不好,文中若有疏漏,请批评...

11620
来自专栏脑洞前端

206. 反转链表

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/reverse-linked-list 著作权归领扣网络...

14620
来自专栏脑洞前端

203. 移除链表元素

https://leetcode-cn.com/problems/remove-linked-list-elements/description/

7920
来自专栏Linyb极客之路

Java性能调优最强实践,让系统飞起来~

Java 应用性能的瓶颈点非常多,比如磁盘、内存、网络 I/O 等系统因素,Java 应用代码,JVM GC,数据库,缓存等。笔者根据个人经验,将 Java 性...

21240
来自专栏Rust语言学习交流

Substrate源码分析:启动流程

我们在命令行启动 substrate 节点,到底发生了什么呢?本文基于 substrate 源码,对其启动流程进行了简单的分析。

12740
来自专栏iOS小生活

依赖管理(二):第三方组件库在Flutter中要如何管理

前面的文章中,我介绍了Flutter工程的资源管理机制。在Flutter中,资源采用先声明后使用的机制,在pubspec.yaml显示地声明资源路径后,才可以使...

25920

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励