前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >到工作中去—项目中如何落地观察者模式

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

作者头像
鹿老师的Java笔记
发布2021-11-16 14:38:54
4990
发布2021-11-16 14:38:54
举报

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

本系列讲解设计模式,不会采用教科书式的顺序逐个讲解,每个设计模式都会基于实际项目代码和业务场景进行讲解,面向实战,并不追求23种设计模式的走马观花。 所以如果你想要全面了解23种设计模式,那么很遗憾这里没有,这样的好文章太多,不缺我一个。 如果你想在自己的项目中落地设计模式,通过设计模式对自己的代码做出提升和优化,那么这里有一个个的实战案例供你学习,通过实际的开发需求以及场景让你学有所用。 本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用

需求背景

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

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

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

image-20210903180036963

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

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

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

image-20210903180434908

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

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

image-20210907170407687

简介

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

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

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

Java实现观察者模式

需求

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

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

不使用设计模式完成需求

库存查询方法

代码语言:javascript
复制
public class InventoryService {

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

主函数

代码语言:javascript
复制
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("调用通知业务方方法");
        }
    }
}
观察者模式完成需求

库存查询方法

代码语言:javascript
复制
public class InventoryService {

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

事件监听接口

代码语言:javascript
复制
public interface EventListener {
    /**
     * @param inventory 库存数据
     */
    void doEvent(String inventory);
}

事件监听实现类

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

事件管理类

代码语言:javascript
复制
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);
        }
    }

}

主函数测试

代码语言:javascript
复制
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中要实现观察者模式的代码非常的简单,具体步骤如下:

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

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

代码语言:javascript
复制
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;
    }

}

订阅发布者,发布事件

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

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

代码语言:javascript
复制
@Component
public class OpenStockPublisher {
//    装配到发布者
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishInventoryEvent(OpenInventory inventory,Integer status) {
//        发布事件对象
        applicationEventPublisher.publishEvent(new StockEvent(inventory,status));
    }

}

定义监听器(订阅),这是最后一步,根据观察者模式,发布事件之后,就需要来订阅消费了,那么如何实现一个订阅消费方法呢,也非常简单,只需要一个注解即可。

代码语言:javascript
复制
/**
 * 库存事件监听器
 */
@Slf4j
@Component
public class StockListener {
   

    /**
     * 批量更新库存
     * @param stockEvent
     */
    @EventListener
    public void addStock(StockEvent stockEvent){
       //省略具体业务代码
    }
    
    /**
     * 设置缺货状态
     * @param stockEvent
     */
    @EventListener
    public void resetStockOut(StockEvent stockEvent){
   //省略具体业务代码
    }

}

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

总结

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

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鹿小洋的Java笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 到工作中去—项目中如何落地观察者模式
    • 需求背景
      • 简介
        • Java实现观察者模式
          • 需求
          • 不使用设计模式完成需求
          • 观察者模式完成需求
        • 在SpringBoot中使用观察者模式
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档