• 让代码变美的第一天 - 观察者模式


    丑陋的模样

    当我们开发一个功能,代码可能如下:

    private void test() {
        func1();
        
        func2();
        
        func3();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 目前看起来还是美美哒,结构清晰,层次分明
      新需求来的时候,慢慢的变成了下面这样:
    private void test() {
        func1();
        try {
            func2();
        } catch (Exception e) {
    
        }
        func3();
    
        func4();
    
        // func5
        if (1 == 1) {
            for (int i = 0; i < 5; i++) {
                // todo1
                // todo2
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 多人开发的话,情况更严重。当项目大了里面的逻辑很少有人能全部讲清楚,老代码没人敢动(劝你也不要动,一年到头辛辛苦苦,背个故障从头来过)
    • 代码会越来越乱,在一个方法里面堆砌的代码会越来越多,如果有洁癖一点的可能还会把新代码封装成func4,如果偷懒可能就变成了func5()下面的代码了,屎山代码可能就是这样出来的吧

    变美步骤

    第一步 - 基本预期

    思考:这个功能,这个方法改动到底是不是很频繁。有个预期就行,如果没有预期,那就看实际改动次数。

    • 如果不频繁,建议就这样吧,只要结构清晰,方便review代码就行。不鼓励过度设计,毕竟开发时间都是有限的。
    • 如果很频繁,就看第二步

    第二步 - 核心逻辑梳理

    还是上面那个代码,需要思考到底哪部分是核心,假设func1、func2、func3是库存校验、下单、修改库存。func4、func5是销量统计、发货
    那么很容易判断出,func123才是核心逻辑,是下单的必要步骤。func45就是下单完之后的一些后续处理,甚至可能都不需要同步处理,比如发货,和下单并没有强绑定。
    很容易就判断出来,这可能是一个发布订阅的逻辑,可以用观察者模式来重构,学了这么多年的设计模式,这不就能用上了么。

    第三步 - 重构

    • 如果未来计划很明确,项目越做越大,肯定要拆分成多个微服务,组成一个分布式服务,并且已经有消息中间件了(Kafka、RocketMQ等),那么就直接上吧,用发布订阅来异步、解耦、削峰。
    • 如果还没有那么明确的目标,也不想引入大型中间件,那么重构一下代码还是很不错的。

    重构1 - 消息定义

    只讨论使用spring项目

    public class OrderEvent {
        private long id;
        private String desc;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 需要向下传递的参数信息

    重构2 - 消息订阅

    @Service
    public class OrderEventListenerService {
        @EventListener
        public void handleForOrderSaveEvent(OrderEvent event) {
            log.info("收到接单消息:{}", event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 这里订阅的就是func4/5

    重构3 - 消息发布

    @Service
    public class OrderEventPublishService {
        @Autowired
        private ApplicationEventPublisher eventPublisher;
    
        public void order() {
            OrderEvent event = new OrderEvent(1, "接单");
            log.info("发送接单消息:{}", event);
            eventPublisher.publishEvent(event);
            log.info("消息发送完毕:{}", event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 这里就是func1/2/3
      三步就搞定了重构,并且下次修改,只需要新增一个订阅方法。

    高级用法

    按顺序订阅

    func4、func5是需要有先后顺序的,那么只需要加上@Order(num),按到num从小到大的顺序先后处理

    @Service
    public class OrderEventListenerService {
        @Order(2)
        @EventListener
        public void handleForOrderSaveEvent1(OrderEvent event) {
            log.info("2同步收到接单消息:{}", event);
        }
    
        @Order(1)
        @EventListener
        public void handleForOrderSaveEvent2(OrderEvent event) {
            log.info("1同步收到接单消息:{}", event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    异步订阅

    如果func5是发货,完全不依赖下单,订阅方就可以异步处理,使用@Async就行了。

    @Service
    public class OrderEventListenerService {
        @Async
        @EventListener
        public void asyncHandleForOrderSaveEvent(OrderEvent event) {
            log.info("异步收到接单消息:{}", event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:使用的是默认线程池,最好改成自定义的线程池

    多消息订阅

    如果func5是发货,除了C端用户下单以外,B端用户也能下单,但是B端下单代码完全是另一份,那么就需要有B端的下单消息,func5统一处理发货流程就行了

    @Service
    public class OrderEventListenerService {
        @Async
        @EventListener(classes = {OrderAEvent.class, OrderBEvent.class})
        public void asyncHandleForOrderSaveEvent(Object event) {
            log.info("异步收到接单消息:{}", event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    【深度学习】不同虚拟环境安装不同Pytorch版本
    【Linux】进程等待
    Web开发:Web开发中的域概念整理与解读
    【初识JavaSe语法笔记起章】——标识符、关键字、字面常量、数据类型、类型转换与提升、字符串类型
    模拟前端ADC芯片LH001-91
    MySQL里为什么会建议不要使用SELECT ?
    编码文字使用整数xyz 三个坐标 并使用
    【uniapp】微信小程序canvas签名旋转生成图片
    Excel如何快速将表格纵向数据变成横向
    《MongoDB》Mongo Shell中的基本操作-更新操作一览
  • 原文地址:https://blog.csdn.net/PROGRAM_anywhere/article/details/134322606