目录
前言
观察者模式对于我们来说,真是再简单不过了。无外乎两个操作,观察者订阅自己关心的主题和主题有数据变化后通知观察者们。
示例
1.原生Java
首先,需要定义主题,每个主题需要持有观察者列表的引用,用于在数据变更的时候通知各个观察者:
public class Subject {
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
// 数据已变更,通知观察者们
notifyAllObservers();
}
// 注册观察者
public void attach(Observer observer) {
observers.add(observer);
}
// 通知观察者们
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
定义观察者接口:
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
其实如果只有一个观察者类的话,接口都不用定义了,不过,通常场景下,既然用到了观察者模式,我们就是希望一个事件出来了,会有多个不同的类需要处理相应的信息。比如,订单修改成功事件,我们希望发短信的类得到通知、发邮件的类得到通知、处理物流信息的类得到通知等。
我们来定义具体的几个观察者类:
public class BinaryObserver extends Observer {
// 在构造方法中进行订阅主题
public BinaryObserver(Subject subject) {
this.subject = subject;
// 通常在构造方法中将 this 发布出去的操作一定要小心
this.subject.attach(this);
}
// 该方法由主题类在数据变更的时候进行调用
@Override
public void update() {
String result = Integer.toBinaryString(subject.getState());
System.out.println("订阅的数据发生变化,新的数据处理为二进制值为:" + result);
}
}
public class HexaObserver extends Observer {
public HexaObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
String result = Integer.toHexString(subject.getState()).toUpperCase();
System.out.println("订阅的数据发生变化,新的数据处理为十六进制值为:" + result);
}
}
客户端使用也非常简单:
public static void main(String[] args) {
// 先定义一个主题
Subject subject1 = new Subject();
// 定义观察者
new BinaryObserver(subject1);
new HexaObserver(subject1);
// 模拟数据变更,这个时候,观察者们的 update 方法将会被调用
subject.setState(11);
}
output:
订阅的数据发生变化,新的数据处理为二进制值为:1011
订阅的数据发生变化,新的数据处理为十六进制值为:B
当然,jdk 也提供了相似的支持,具体的大家可以参考 java.util.Observable 和 java.util.Observer 这两个类。
实际生产过程中,观察者模式往往用消息中间件来实现,如果要实现单机观察者模式,笔者建议读者使用 Guava 中的 EventBus,它有同步实现也有异步实现,本文主要介绍设计模式,就不展开说了。
还有,即使是上面的这个代码,也会有很多变种,大家只要记住核心的部分,那就是一定有一个地方存放了所有的观察者,然后在事件发生的时候,遍历观察者,调用它们的回调函数。
2.jdk-Observable&Observer
现在很多的购房者都在关注着房子的价格变化,每当房子价格变化的时候,所有的购房者都可以观察得到。
实际上以上的购房者就是观察者,他们所关注的房价就是被观察者。
其中要求,被观察者需要继承Observable类,观察则需要实现Observer接口
具体实现如下
房价的实现
1 class House extends Observable{ 2 private double price; 3 public House(double price){ 4 this.price=price; 5 } 6 public double getPrice(){ 7 return price; 8 } 9 public void setPrice(double price){ 10 if(this.price!=price){ 11 this.price=price; 12 setChanged(); //标注价格已经被更改 13 this.notifyObservers(price); //通知观察者数据已被更改 14 } 15 } 16 @Override 17 public String toString() { 18 return "当前房价为:"+price; 19 } 20 }
购房者实现
1 class HousePriceObserver implements Observer{ 2 private String name; 3 public HousePriceObserver(String name) { 4 this.name=name; 5 } 6 @Override 7 public void update(Observable o, Object arg) { 8 //这里最好判断一下通知是否来自于房价,有可能来自其它地方 9 if(o instanceof House){ 10 System.out.println("购物者"+name+ "观察到房价已调整为:"+arg); 11 } 12 } 13 }
运行
// 定义主题 1 House house=new House(10000); // 定义三个观察者 2 HousePriceObserver A=new HousePriceObserver("A"); 3 HousePriceObserver B=new HousePriceObserver("B"); 4 HousePriceObserver C=new HousePriceObserver("C"); // 主题 与 主题的观察者们 5 house.addObserver(A); 6 house.addObserver(B); 7 house.addObserver(C); 8 System.out.println(house); // 发布事件通知 9 house.setPrice(6000); 10 house.setPrice(8000);
运行结果为:
当前房价为:10000.0
购物者C观察到房价已调整为:6000.0
购物者B观察到房价已调整为:6000.0
购物者A观察到房价已调整为:6000.0
购物者C观察到房价已调整为:8000.0
购物者B观察到房价已调整为:8000.0
购物者A观察到房价已调整为:8000.0
2.spring-Observable&Observer
‘主题’ & ‘主题的观察者们’:
package com.atta.msgdactuator.notification;
import com.atta.infra.msgdactuator.api.event.StatisticsEventMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Observable;
/**
* 邮件生命周期事件发布者
*/
@Component
@Slf4j
public class EmailLifeCycleEventPublisher extends Observable implements InitializingBean {
@Autowired
private EmailLifeCycleEventListener emailLifeCycleEventObserver;
@Override
public void afterPropertiesSet() throws Exception {
this.addObserver(emailLifeCycleEventObserver);
}
/**
* 功能描述:事件通知。REPLIED,UNSUBSCRIBE,HARDBACK,SOFTBACK,NOT_SEND,SEND共计6个事件。
*
* 声明:
* 1、异常由【调用事件发布者处】进行捕获
*
* @param statisticsEventMessage req
*/
public void notify(StatisticsEventMessage statisticsEventMessage) {
if (statisticsEventMessage == null){
return;
}
super.setChanged();
// 功能描述:1、内部是通过调用'订阅者'的update方法来进行通知的
super.notifyObservers(statisticsEventMessage);
}
}
观察者(此处指定义了一个观察者):
package com.atta.msgdactuator.notification;
import com.atta.infra.msgdactuator.api.event.StatisticsEventMessage;
import com.atta.msgdactuator.service.MessageTrackingStatisticsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Observable;
import java.util.Observer;
/**
* 事件监听者
* 职责:处理EmailLifeCycleEventPublisher 发布出来的StatisticsEventMessage事件
*/
@Component
@Slf4j
public class EmailLifeCycleEventListener implements Observer {
@Value("${email.event.track.real.time.statistic.enabled:true}")
private Boolean realTimeStatisticEnable;
@Autowired
private MessageTrackingStatisticsService messageTrackingStatisticsService;
/**
* 功能描述:订阅来自发布者的事件通知。REPLIED,UNSUBSCRIBE,HARDBACK,SOFTBACK,NOT_SEND,SEND共计6个事件。
*
* 声明:异常由【调用事件发布者处】进行捕获
*
* @param publisher
* @param event
*/
@Override
public void update(Observable publisher, Object event) {
if (publisher instanceof EmailLifeCycleEventPublisher && event instanceof StatisticsEventMessage) {
// 参数说明:realTimeStatisticEnable是指是否开启事件实时统计开关(默认true-开启)
if(realTimeStatisticEnable) {
messageTrackingStatisticsService.notifyStatisticsEventMessage((StatisticsEventMessage) event);
}
}
}
}
调用处:
@Service
@Slf4j
public class MessageTrackingStatisticsFacadeImpl implements MessageTrackingStatisticsFacade {
@Autowired
private MessageTrackingStatisticsService messageTrackingStatisticsService;
@Autowired
private EmailLifeCycleEventPublisher emailLifeCycleEventPublisher;
@Override
@AttaLogger(name = "MessageTrackingStatisticsFacadeImpl.notifyUnSubscribeStatisticsMsg", value = {"${param}", "${ret}"})
public FacadeResponse<Void> notifyUnSubscribeStatisticsMsg(StatisticsEventMessage message) {
FacadeResponse<Void> facadeResponse = new FacadeResponse<>();
facadeResponse.setSucceeded(true);
try{
emailLifeCycleEventPublisher.notify(message);
} catch (Exception e) {
log.error("[MessageTrackingStatisticsFacadeImpl#notifyUnSubscribeStatisticsMsg] fail to notifyStatisticsMessage. taskId:[{}], subject:[{}], eventType:[{}], mid:[{}]",
message.getTaskId(), message.getSubject(), message.getType(), message.getMid(), e);
facadeResponse.setSucceeded(false);
facadeResponse.setResponseMsg(e.getMessage());
}
return facadeResponse;
}
}
3.springboot-event
总结
优点:
● 观察者和被观察者之间是抽象耦合 如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定 义,在系统扩展方面更是得心应手。
● 建立一套触发机制 根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关 系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争 抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘……这就是一个 触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。
缺点:
观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复 杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一 般考虑采用异步的方式。
使用场景:
● 事件多级触发场景。
● 跨系统的消息交换场景,如消息队列的处理机制
使用案例:spring 事件广播机制就是使用了观察者模式。