本系列讲解设计模式,不会采用教科书式的顺序逐个讲解,每个设计模式都会基于实际项目代码和业务场景进行讲解,面向实战,并不追求23种设计模式的走马观花。 所以如果你想要全面了解23种设计模式,那么很遗憾这里没有,这样的好文章太多,不缺我一个。 如果你想在自己的项目中落地设计模式,通过设计模式对自己的代码做出提升和优化,那么这里有一个个的实战案例供你学习,通过实际的开发需求以及场景让你学有所用。 本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用
有这样一个场景:需要通过定时任务从第三方获取库存数据,拿到库存数据之后,并不是简单的更新数据库,而是需要做至少三个事情:
基于这样的一个场景,目前项目中采用的是同步调用的方式:先写库存,再更新状态,再通知业务方,这样一种做法从功能实现上来说没有问题,可以实现需求的效果。
image-20210903180036963
注:由于项目业务的要求,实际上从第三方获取到库存数据是共享库存,并不要求三个业务方法按顺序执行
但是这样的代码性能和稳定性差,并且很难做扩展,例如我想对库存更新做批量更新,目前的代码结构就做不了
所以就想要解耦,不希望库存数据和三个处理方法太紧密,想要分开可以更加灵活的处理,那么最简单的方案就是因为队列,将查询到的库存数据直接放入队列中,三个处理业务都订阅这个队列,进行处理,至于业务获取到数据之后是单个添加还是批量添加都可以。
image-20210903180434908
这样的一种发布订阅的模式,对于后期扩展来说也会非常友好,而且可以针对不同的业务增加异步,重试,批量等优化手段,提高代码执行的效率。
上述所说的发布订阅模式,如果不采用MQ,纯Java实现的话,就是观察者模式。
image-20210907170407687
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。
观察者模式的概念不复杂,但是想要应用到项目中,就不容易了,所以接下来我们通过一些代码来学习观察者模式的使用。
根据开篇的项目需求背景,我们来设计一个简单的需求。
批量获取库存数据之后,需要做两个事情,一个是更新库存,另一个是通知业务方。
库存查询方法
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 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);
}
}
}
image-20210906171210488
对于观察者模式,由于其编码的复杂度,想要通过自己写观察者模式并整合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的场景都可以尝试考虑一下观察者设计模式。
本系列的宗旨是:从实际开发中来,到实际开发中去,学了工作就有用