本文正在参与 “性能优化实战记录”话题征文活动
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
到工作中去—项目中如何落地观察者模式
本系列讲解设计模式,不会采用教科书式的顺序逐个讲解,每个设计模式都会基于实际项目代码和业务场景进行讲解,面向实战,并不追求23种设计模式的走马观花。
所以如果你想要全面了解23种设计模式,那么很遗憾这里没有,这样的好文章太多,不缺我一个。
如果你想在自己的项目中落地设计模式,通过设计模式对自己的代码做出提升和优化,那么这里有一个个的实战案例供你学习,通过实际的开发需求以及场景让你学有所用。
本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用
需求背景
有这样一个场景:需要通过定时任务从第三方获取库存数据,拿到库存数据之后,并不是简单的更新数据库,而是需要做至少三个事情:
- 更新库存数据
- 更新sku表中对应sku的状态信息(是否缺货)
- 通知自己的业务方最新的库存数据
基于这样的一个场景,目前项目中采用的是同步调用的方式:先写库存,再更新状态,再通知业务方,这样一种做法从功能实现上来说没有问题,可以实现需求的效果。
注:由于项目业务的要求,实际上从第三方获取到库存数据是共享库存,并不要求三个业务方法按顺序执行
但是这样的代码性能和稳定性差,并且很难做扩展,例如我想对库存更新做批量更新,目前的代码结构就做不了
所以就想要解耦,不希望库存数据和三个处理方法太紧密,想要分开可以更加灵活的处理,那么最简单的方案就是因为队列,将查询到的库存数据直接放入队列中,三个处理业务都订阅这个队列,进行处理,至于业务获取到数据之后是单个添加还是批量添加都可以。
这样的一种发布订阅的模式,对于后期扩展来说也会非常友好,而且可以针对不同的业务增加异步,重试,批量等优化手段,提高代码执行的效率。
上述所说的发布订阅模式,如果不采用MQ,纯Java实现的话,就是观察者模式。
简介
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。
观察者模式的概念不复杂,但是想要应用到项目中,就不容易了,所以接下来我们通过一些代码来学习观察者模式的使用。
Java实现观察者模式
需求
根据开篇的项目需求背景,我们来设计一个简单的需求。
批量获取库存数据之后,需要做两个事情,一个是更新库存,另一个是通知业务方。
不使用设计模式完成需求
-
库存查询方法
public class InventoryService { /** * 模拟获取库存数据 */ public List<String> getInventory(){ System.out.println("获取到库存数据"); return Arrays.asList("1","2","3"); } } 复制代码
-
主函数
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("调用通知业务方方法"); } } } 复制代码
观察者模式完成需求
-
库存查询方法
public class InventoryService { /** * 模拟获取库存数据 */ public List<String> getInventory(){ System.out.println("获取到库存数据"); return Arrays.asList("1","2","3"); } } 复制代码
-
事件监听接口
public interface EventListener { /** * @param inventory 库存数据 */ void doEvent(String inventory); } 复制代码
-
事件监听实现类
-
更新库存实现类
public class UpdateEventListener implements EventListener{ @Override public void doEvent(String inventory) { System.out.println("更新库存数据"); } } 复制代码
-
通知业务方实现类
public class MessageEventListener implements EventListener{ @Override public void doEvent(String inventory) { System.out.println("发送消息通知给业务方"); } } 复制代码
-
-
事件管理类
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); } } } 复制代码
-
主函数测试
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中使用观察者模式
对于观察者模式,由于其编码的复杂度,想要通过自己写观察者模式并整合Spring应用到项目中,无疑是非常困难的,所以SpringBoot针对观察者模式也做了很多的封装,让我们通过少量代码和注解非常快捷的实现观察者模式。
在SpringBoot中要实现观察者模式的代码非常的简单,具体步骤如下:
-
定义事件,首先需要定义一个事件,通过事件封装我们要通过观察者模式发布的对象,代码如下,需要继承 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; } } 复制代码
-
订阅发布者,发布事件
观察者模式需要通过代码来发布事件对象,然后观察者接收到事件对象进行处理。
在SpringBoot中要发布事件对象也非常的简单,只需要装配SpringBoot定义好的 ApplicationEventPublisher 即可,代码如下
@Component public class OpenStockPublisher { // 装配到发布者 @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishInventoryEvent(OpenInventory inventory,Integer status) { // 发布事件对象 applicationEventPublisher.publishEvent(new StockEvent(inventory,status)); } } 复制代码
-
定义监听器(订阅),这是最后一步,根据观察者模式,发布事件之后,就需要来订阅消费了,那么如何实现一个订阅消费方法呢,也非常简单,只需要一个注解即可。
/** * 库存事件监听器 */ @Slf4j @Component public class StockListener { /** * 批量更新库存 * @param stockEvent */ @EventListener public void addStock(StockEvent stockEvent){ //省略具体业务代码 } /** * 设置缺货状态 * @param stockEvent */ @EventListener public void resetStockOut(StockEvent stockEvent){ //省略具体业务代码 } } 复制代码
通过以上三步,就实现了观察者模式。
总结
在我看来,设计模式存在的意义就是在特定场景下解决特定的问题,场景非常的重要,如果使用的场景不对,对于解决问题往往会南辕北辙,使用错误的设计模式很多时候会让事情更加的麻烦,关于这一点,在下一篇文章中,通过另一个具体的开发案例进行讲解,论述一下错误的使用工厂设计模式造成的结果,以及如何通过责任链模式更加简单的解决问题。
最后,一句话总结一下观察者设计模式的使用场景:可以使用MQ的场景都可以尝试考虑一下观察者设计模式。
本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用