到工作中去—项目中如何落地观察者模式

698 阅读7分钟

本文正在参与 “性能优化实战记录”话题征文活动

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

到工作中去—项目中如何落地观察者模式

本系列讲解设计模式,不会采用教科书式的顺序逐个讲解,每个设计模式都会基于实际项目代码和业务场景进行讲解,面向实战,并不追求23种设计模式的走马观花。

所以如果你想要全面了解23种设计模式,那么很遗憾这里没有,这样的好文章太多,不缺我一个。

如果你想在自己的项目中落地设计模式,通过设计模式对自己的代码做出提升和优化,那么这里有一个个的实战案例供你学习,通过实际的开发需求以及场景让你学有所用。

本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用

需求背景

有这样一个场景:需要通过定时任务从第三方获取库存数据,拿到库存数据之后,并不是简单的更新数据库,而是需要做至少三个事情:

  1. 更新库存数据
  2. 更新sku表中对应sku的状态信息(是否缺货)
  3. 通知自己的业务方最新的库存数据

基于这样的一个场景,目前项目中采用的是同步调用的方式:先写库存,再更新状态,再通知业务方,这样一种做法从功能实现上来说没有问题,可以实现需求的效果。

image-20210903180036963

注:由于项目业务的要求,实际上从第三方获取到库存数据是共享库存,并不要求三个业务方法按顺序执行

但是这样的代码性能和稳定性差,并且很难做扩展,例如我想对库存更新做批量更新,目前的代码结构就做不了

所以就想要解耦,不希望库存数据和三个处理方法太紧密,想要分开可以更加灵活的处理,那么最简单的方案就是因为队列,将查询到的库存数据直接放入队列中,三个处理业务都订阅这个队列,进行处理,至于业务获取到数据之后是单个添加还是批量添加都可以。

image-20210903180434908

这样的一种发布订阅的模式,对于后期扩展来说也会非常友好,而且可以针对不同的业务增加异步,重试,批量等优化手段,提高代码执行的效率。

上述所说的发布订阅模式,如果不采用MQ,纯Java实现的话,就是观察者模式

image-20210907170407687

简介

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。

观察者模式的概念不复杂,但是想要应用到项目中,就不容易了,所以接下来我们通过一些代码来学习观察者模式的使用。

Java实现观察者模式

需求

根据开篇的项目需求背景,我们来设计一个简单的需求。

批量获取库存数据之后,需要做两个事情,一个是更新库存,另一个是通知业务方。

不使用设计模式完成需求

  1. 库存查询方法

    public class InventoryService {
    
        /**
         * 模拟获取库存数据
         */
        public List<String> getInventory(){
            System.out.println("获取到库存数据");
            return Arrays.asList("1","2","3");
        }
    }
    
  2. 主函数

    public class App {
        public static void main( String[] args ) {
            InventoryService inventoryService = new InventoryService();
            List<String> inventorys = inventoryService.getInventory();
            for (String inventory : inventorys) {
                System.out.println(inventory);
                System.out.println("调用更新库存方法");
                System.out.println("调用通知业务方方法");
            }
        }
    }
    

观察者模式完成需求

  1. 库存查询方法

    public class InventoryService {
    
        /**
         * 模拟获取库存数据
         */
        public List<String> getInventory(){
            System.out.println("获取到库存数据");
            return Arrays.asList("1","2","3");
        }
    }
    
  2. 事件监听接口

    public interface EventListener {
        /**
         * @param inventory 库存数据
         */
        void doEvent(String inventory);
    }
    
  3. 事件监听实现类

    1. 更新库存实现类

      public class UpdateEventListener implements EventListener{
          @Override
          public void doEvent(String inventory) {
              System.out.println("更新库存数据");
          }
      }
      
    2. 通知业务方实现类

      public class MessageEventListener implements EventListener{
      
          @Override
          public void doEvent(String inventory) {
              System.out.println("发送消息通知给业务方");
          }
      }
      
  4. 事件管理类

    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 {
            DB, 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, String result) {
            List<EventListener> users = listeners.get(eventType);
            for (EventListener listener : users) {
                listener.doEvent(result);
            }
        }
    
    }
    
  5. 主函数测试

    public class App {
        public static void main( String[] args ) {
            EventManager eventManager = new EventManager(EventManager.EventType.DB, EventManager.EventType.Message);
            eventManager.subscribe(EventManager.EventType.DB, new UpdateEventListener());
            eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
    
            InventoryService inventoryService = new InventoryService();
            List<String> inventory = inventoryService.getInventory();
            for (String s : inventory) {
                eventManager.notify(EventManager.EventType.DB,s);
                eventManager.notify(EventManager.EventType.Message,s);
            }
    
        }
    }
    

在SpringBoot中使用观察者模式

image-20210906171210488

对于观察者模式,由于其编码的复杂度,想要通过自己写观察者模式并整合Spring应用到项目中,无疑是非常困难的,所以SpringBoot针对观察者模式也做了很多的封装,让我们通过少量代码和注解非常快捷的实现观察者模式。

在SpringBoot中要实现观察者模式的代码非常的简单,具体步骤如下:

  1. 定义事件,首先需要定义一个事件,通过事件封装我们要通过观察者模式发布的对象,代码如下,需要继承 ApplicationEvent。

    构造方法中的source属性就是要发布订阅的对象,如果有多个对象要进行传递,我们也可以在事件对象中进行自定义

    public class StockEvent extends ApplicationEvent {
    		// 自定义属性
        private Integer status;
        /**
         * Create a new ApplicationEvent.
         *
         * @param source the object on which the event initially occurred (never {@code null})
         */
        public StockEvent(Object source,Integer status) {
            super(source);
            this.status = status;
        }
    
        public Integer getStatus() {
            return status;
        }
    
    }
    
  2. 订阅发布者,发布事件

    观察者模式需要通过代码来发布事件对象,然后观察者接收到事件对象进行处理。

    在SpringBoot中要发布事件对象也非常的简单,只需要装配SpringBoot定义好的 ApplicationEventPublisher 即可,代码如下

    @Component
    public class OpenStockPublisher {
    //    装配到发布者
        @Autowired
        private ApplicationEventPublisher applicationEventPublisher;
    
        public void publishInventoryEvent(OpenInventory inventory,Integer status) {
    //        发布事件对象
            applicationEventPublisher.publishEvent(new StockEvent(inventory,status));
        }
    
    }
    
  3. 定义监听器(订阅),这是最后一步,根据观察者模式,发布事件之后,就需要来订阅消费了,那么如何实现一个订阅消费方法呢,也非常简单,只需要一个注解即可。

    /**
     * 库存事件监听器
     */
    @Slf4j
    @Component
    public class StockListener {
       
    
        /**
         * 批量更新库存
         * @param stockEvent
         */
        @EventListener
        public void addStock(StockEvent stockEvent){
           //省略具体业务代码
        }
        
        /**
         * 设置缺货状态
         * @param stockEvent
         */
        @EventListener
        public void resetStockOut(StockEvent stockEvent){
    			//省略具体业务代码
        }
    
    }
    

通过以上三步,就实现了观察者模式。

总结

在我看来,设计模式存在的意义就是在特定场景下解决特定的问题,场景非常的重要,如果使用的场景不对,对于解决问题往往会南辕北辙,使用错误的设计模式很多时候会让事情更加的麻烦,关于这一点,在下一篇文章中,通过另一个具体的开发案例进行讲解,论述一下错误的使用工厂设计模式造成的结果,以及如何通过责任链模式更加简单的解决问题。

最后,一句话总结一下观察者设计模式的使用场景:可以使用MQ的场景都可以尝试考虑一下观察者设计模式。

本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用