作者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章
沉淀、分享、成长,让自己和他人都能有所收获!😄
知道的越多不知道的就越多
编程开发这条路上的知识是无穷无尽的,就像以前你敢说精通Java,到后来学到越来越多只想写了解Java,过了几年现在可能想说懂一点点Java。当视野和格局的扩大,会让我们越来越发现原来的看法是多么浅显,这就像站在地球看地球和站在宇宙看地球一样。但正因为胸怀和眼界的提升让我们有了更多的认识,也逐渐学会了更多的技能。虽然不知道的越来越多,但也因此给自己填充了更多的技术栈,让自己越来越强大。
拒绝学习的惰性很可怕
现在与以前不一样,资料多、途径广,在这中间夹杂的广告也非常多。这就让很多初学者很难找到自己要的知识,最后看到有人推荐相关学习资料立刻屏蔽、删除,但同时技术优秀的资料也不能让需要的人看见了。久而久之把更多的时间精力都放在游戏、娱乐、影音上,适当的放松是可以的,但往往沉迷以后就很难出来,因此需要做好一些可以让自己成长的计划,稍有克制。
平衡好软件设计和实现成本的度°
有时候一个软件的架构设计需要符合当前条件下的各项因素,往往不能因为心中想当然的有某个蓝图,就去开始执行。也许虽然你的设计是非常优秀的,但是放在当前环境下很难满足业务的时间要求,当一个业务的基本诉求不能满足后,就很难拉动市场。没有产品的DAU支撑,最后整个研发的项目也会因此停滞。但研发又不能一团乱麻的写代码,因此需要找好一个适合的度,比如可以搭建良好的地基,实现上可扩展。但在具体的功能上可以先简化实现,随着活下来了再继续完善迭代。
bugstack虫洞栈
,回复源码下载
获取(打开获取的链接,找到序号18)工程 | 描述 |
---|---|
itstack-demo-design-18-00 | 场景模拟工程;模拟一个小客车摇号接口 |
itstack-demo-design-18-01 | 使用一坨代码实现业务需求 |
itstack-demo-design-18-02 | 通过设计模式优化改造代码,产生对比性从而学习 |
简单来讲观察者🕵模式,就是当一个行为发生时传递信息给另外一个用户接收做出相应的处理,两者之间没有直接的耦合关联。例如;狙击手、李云龙。
除了生活中的场景外,在我们编程开发中也会常用到一些观察者的模式或者组件,例如我们经常使用的MQ服务,虽然MQ服务是有一个通知中心并不是每一个类服务进行通知,但整体上也可以算作是观察者模式的思路设计。再比如可能有做过的一些类似事件监听总线,让主线服务与其他辅线业务服务分离,为了使系统降低耦合和增强扩展性,也会使用观察者模式进行处理。
在本案例中我们模拟每次小客车指标摇号事件通知场景(真实的不会由官网给你发消息)
可能大部分人看到这个案例一定会想到自己每次摇号都不中的场景,收到一个遗憾的短信通知。当然目前的摇号系统并不会给你发短信,而是由百度或者一些其他插件发的短信。那么假如这个类似的摇号功能如果由你来开发,并且需要对外部的用户做一些事件通知以及需要在主流程外再添加一些额外的辅助流程时该如何处理呢?
基本很多人对于这样的通知事件类的实现往往比较粗犷,直接在类里面就添加了。1是考虑🤔这可能不会怎么扩展,2是压根就没考虑😄过。但如果你有仔细思考过你的核心类功能会发现,这里面有一些核心主链路,还有一部分是辅助功能。比如完成了某个行为后需要触发MQ给外部,以及做一些消息PUSH给用户等,这些都不算做是核心流程链路,是可以通过事件通知的方式进行处理。
那么接下来我们就使用这样的设计模式来优化重构此场景下的代码。
itstack-demo-design-18-00 └── src └── main └── java └── org.itstack.demo.design └── MinibusTargetService.java
public class MinibusTargetService { /** * 模拟摇号,但不是摇号算法 * * @param uId 用户编号 * @return 结果 */ public String lottery(String uId) { return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码".concat(uId).concat("在本次摇号中签") : "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期"); } }
这里我们先使用最粗暴的方式来实现功能
按照需求需要在原有的摇号接口中添加MQ消息发送以及短消息通知功能,如果是最直接的方式那么可以直接在方法中补充功能即可。
itstack-demo-design-18-01 └── src └── main └── java └── org.itstack.demo.design ├── LotteryResult.java ├── LotteryService.java └── LotteryServiceImpl.java
LotteryResult
)、定义接口(LotteryService
)、具体实现(LotteryServiceImpl
)。public class LotteryServiceImpl implements LotteryService { private Logger logger = LoggerFactory.getLogger(LotteryServiceImpl.class); private MinibusTargetService minibusTargetService = new MinibusTargetService(); public LotteryResult doDraw(String uId) { // 摇号 String lottery = minibusTargetService.lottery(uId); // 发短信 logger.info("给用户 {} 发送短信通知(短信):{}", uId, lottery); // 发MQ消息 logger.info("记录用户 {} 摇号结果(MQ):{}", uId, lottery); // 结果 return new LotteryResult(uId, lottery, new Date()); } }
摇号
接口调用外,后面的两部分都是非核心主链路功能,而且会随着后续的业务需求发展而不断的调整和扩充,在这样的开发方式下就非常不利于维护。@Test public void test() { LotteryService lotteryService = new LotteryServiceImpl(); LotteryResult result = lotteryService.doDraw("2765789109876"); logger.info("测试结果:{}", JSON.toJSONString(result)); }
22:02:24.520 [main] INFO o.i.demo.design.LotteryServiceImpl - 给用户 2765789109876 发送短信通知(短信):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期 22:02:24.523 [main] INFO o.i.demo.design.LotteryServiceImpl - 记录用户 2765789109876 摇号结果(MQ):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期 22:02:24.606 [main] INFO org.itstack.demo.design.ApiTest - 测试结果:{"dateTime":1598764144524,"msg":"很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期","uId":"2765789109876"} Process finished with exit code 0
接下来使用观察者模式来进行代码优化,也算是一次很小的重构。
itstack-demo-design-18-02 └── src └── main └── java └── org.itstack.demo.design ├── event │ ├── listener │ │ ├── EventListener.java │ │ ├── MessageEventListener.java │ │ └── MQEventListener.java │ └── EventManager.java ├── LotteryResult.java ├── LotteryService.java └── LotteryServiceImpl.java
观察者模式模型结构
事件监听
、事件处理
、具体的业务流程
,另外在业务流程中 LotteryService
定义的是抽象类,因为这样可以通过抽象类将事件功能屏蔽,外部业务流程开发者不需要知道具体的通知操作。public interface EventListener { void doEvent(LotteryResult result); }
<T>
短消息事件
public class MessageEventListener implements EventListener { private Logger logger = LoggerFactory.getLogger(MessageEventListener.class); @Override public void doEvent(LotteryResult result) { logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg()); } }
MQ发送事件
public class MQEventListener implements EventListener { private Logger logger = LoggerFactory.getLogger(MQEventListener.class); @Override public void doEvent(LotteryResult result) { logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg()); } }
public class EventManager { Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>(); public EventManager(Enum<EventType>... operations) { for (Enum<EventType> operation : operations) { this.listeners.put(operation, new ArrayList<>()); } } public enum EventType { MQ, Message } /** * 订阅 * @param eventType 事件类型 * @param listener 监听 */ public void subscribe(Enum<EventType> eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.add(listener); } /** * 取消订阅 * @param eventType 事件类型 * @param listener 监听 */ public void unsubscribe(Enum<EventType> eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.remove(listener); } /** * 通知 * @param eventType 事件类型 * @param result 结果 */ public void notify(Enum<EventType> eventType, LotteryResult result) { List<EventListener> users = listeners.get(eventType); for (EventListener listener : users) { listener.doEvent(result); } } }
subscribe
)、取消订阅(unsubscribe
)、通知(notify
)。这三个方法分别用于对监听时间的添加和使用。EventType.MQ
、EventType.Message
)。public abstract class LotteryService { private EventManager eventManager; public LotteryService() { eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message); eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener()); eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener()); } public LotteryResult draw(String uId) { LotteryResult lotteryResult = doDraw(uId); // 需要什么通知就给调用什么方法 eventManager.notify(EventManager.EventType.MQ, lotteryResult); eventManager.notify(EventManager.EventType.Message, lotteryResult); return lotteryResult; } protected abstract LotteryResult doDraw(String uId); }
abstract LotteryResult doDraw(String uId)
,让类的继承者实现。protected
,也就是保证将来外部的调用方不会调用到此方法,只有调用到draw(String uId)
,才能让我们完成事件通知。eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener())
。EventManager.EventType.MQ
,就会执行什么事件通知,按需添加。public class LotteryServiceImpl extends LotteryService { private MinibusTargetService minibusTargetService = new MinibusTargetService(); @Override protected LotteryResult doDraw(String uId) { // 摇号 String lottery = minibusTargetService.lottery(uId); // 结果 return new LotteryResult(uId, lottery, new Date()); } }
@Test public void test() { LotteryService lotteryService = new LotteryServiceImpl(); LotteryResult result = lotteryService.draw("2765789109876"); logger.info("测试结果:{}", JSON.toJSONString(result)); }
23:56:07.597 [main] INFO o.i.d.d.e.listener.MQEventListener - 记录用户 2765789109876 摇号结果(MQ):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期 23:56:07.600 [main] INFO o.i.d.d.e.l.MessageEventListener - 给用户 2765789109876 发送短信通知(短信):很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期 23:56:07.698 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"dateTime":1599737367591,"msg":"很遗憾,编码2765789109876在本次摇号未中签或摇号资格已过期","uId":"2765789109876"} Process finished with exit code 0
营销
、裂变
、促活
等等,因此使用设计模式架设代码就显得非常有必要。1. 重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」
2. 重学 Java 设计模式:实战原型模式「上机考试多套试,每人题目和答案乱序排列场景」
3. 重学 Java 设计模式:实战桥接模式「多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)场景」
4. 重学 Java 设计模式:实战组合模式「营销差异化人群发券,决策树引擎搭建场景」
5. 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」
6. 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
7. 重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」