什么是观察者模式?
观察者模式(Observer Pattern)定义了对象间的一种一对多的依赖关系,这样只要一个对象的状态发生改变,其依赖的所有相关对象都会得到通知并自动更新。
在观察者模式中,发生改变的对象叫做观察目标,而被通知更新的对象称为观察者,一个观察目标对应多个观察者,观察者一般是一个列表集合,可以根据需要动态增加和删除,易于扩展。
使用观察者模式的优点在于观察目标和观察者之间是抽象松耦合关系,降低了两者之间的耦合关系。
发布-订阅模式
观察者模式很多地方也叫发布-订阅模式(Publish/Subscribe),其实也可以这么理解,不过两者之间还是略有不同。
观察者模式中的观察者是直接绑定观察目标,观察目标要维护一套观察者列表,两者是有一个基于接口的组合依赖关系的,所以说观察者模式虽然是松耦合的,但并不是完全解耦的。
而发布-订阅模式中的发布者和订阅者两者并没有任何联系,发布者通过中间方发布一个主题(Topic),订阅者通过中间方(调度中心)订阅一个主题(Topic),发布者状态的变更也并不会直接通知订阅者,而要通过中间方进行通知,或者订阅者自行从中间方拉取,所以说发布-订阅模式是完全解耦的。
一图搞懂它们的关系:
观察者模式轮子
因观察者模式应用比较广泛,所以 JDK 工具包从 1.0 版本里面自带了观察者模式模板套装,我们根据其模板很方便就能实现观察者模式,不需要再重复造轮子了。
观察者目标类:
java.util.Observable
里面两个最重要的变量:
-
changed:观察目标状态是否变更,默认为:false;
-
obs:观察者列表(observers),一个线程安全的列表集合:Vector,默认为空集合;
里面的重要的方法都是和观察目标状态和观察者相关的,一看就清楚,这里就不介绍了。
观察者接口:
java.util.Observable
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
观察者接口只有一个 update 方法,用来通知观察者自己更新。
观察者模式实战
OK,知道了 JDK 自带了这两个东东,现在就来实现一个简单的观察者模式的应用场景,模拟公众号文章推送,观察目标是栈长我,观察者是你们大家,我发布一篇文章,你们都能接收到更新通知并能阅读。
新增观察目标类:
import lombok.Getter;
import java.util.Observable;
/**
* 观察目标:栈长
*/
@Getter
public class JavaStackObservable extends Observable {
private String article;
/**
* 发表文章
* @param article
*/
public void publish(String article){
// 发表文章
this.article = article;
// 改变状态
this.setChanged();
// 通知所有观察者
this.notifyObservers();
}
}
观察目标的逻辑是先发表文章,再改变观察目标的状态,再通知所有观察者。
我们来重点看 notifyObservers 方法的源码:
先获取同步锁,判断状态是否更新,如已更新则清空观察目标状态,然后再使用 for 循环遍历所有观察者,一一调用观察者的更新方法通知观察者更新。
新增观察者类:
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Observable;
import java.util.Observer;
/**
* 观察者:读者粉丝
*/
@RequiredArgsConstructor
public class ReaderObserver implements Observer {
@NonNull
private String name;
private String article;
@Override
public void update(Observable o, Object arg) {
// 更新文章
updateArticle(o);
}
private void updateArticle(Observable o) {
JavaStackObservable javaStackObservable = (JavaStackObservable) o;
this.article = javaStackObservable.getArticle();
System.out.printf("我是读者:%s,文章已更新为:%s\n", this.name, this.article);
}
}
观察者的逻辑是获取到观察者目标实例对象,然后再用观察目标对象的文章信息更新为自己的文章信息,最后输出某某某的文章已更新。
观察者只要实现 Observer 这个接口的 update 方法即可,用于观察目标进行调用通知。
观察目标和观察者类结构图如下:
新增测试类:
/**
* 观察者:读者粉丝
*/
public class ObserverTest {
public static void main(String[] args) {
// 创建一个观察目标
JavaStackObservable javaStackObservable = new JavaStackObservable();
// 添加观察者
javaStackObservable.addObserver(new ReaderObserver("小明"));
javaStackObservable.addObserver(new ReaderObserver("小张"));
javaStackObservable.addObserver(new ReaderObserver("小爱"));
// 发表文章
javaStackObservable.publish("什么是观察者模式?");
}
}
观察目标、观察者的创建并没有先后顺序要求,重点是发表文章通知观察者之前,观察目标要添加观察者列表这一步不能少。
输出结果:
通过这个简单的文章推送实践,大家应该对观察者模式有一个基本的认知了,在实际工作当中也可以有很多场景拿去用,就一对多的依赖关系都可以考虑使用观察者模式。
其他例子:
抽象目标类典型代码如下:
import java.util.List;
import java.util.ArrayList;
public abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected List<Observer> observers = new ArrayList();
//注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer) {
observers.add(observer);
}
//注销方法,用于在观察者集合中删除一个观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//声明抽象通知方法
public abstract void notify();
}
具体目标类典型代码如下:
public class ConcreteSubject extends Subject {
//实现通知方法
public void notify() {
//遍历观察者集合,调用每一个观察者的响应方法
for(Observer obs:observers) {
obs.update();
}
}
}
抽象观察者典型代码如下:
public interface Observer {
//声明响应方法
public void update();
}
具体观察者典型代码如下:
public class ConcreteObserver implements Observer {
//实现响应方法
public void update() {
//具体响应代码
}
}
- 有时候在具体观察者类ConcreteObserver中需要使用到具体目标类ConcreteSubject中的状态(属性),会存在关联或依赖关系。
- 如果在具体层之间具有关联关系,系统的扩展性将受到一定的影响,增加新的具体目标类有时候需要修改原有观察者的代码,在一定程度上违背了开闭原则,但是如果原有观察者类无须关联新增的具体目标,则系统扩展性不受影响。
/**
* 观察者的顶层接口
* @param <T>
*/
public interface ObserverInterface<T> {
//注册监听者
public void registerListener(T t);
//移除监听者
public void removeListener(T t);
//通知监听者
public void notifyListener(DataEvent t);
}
定义抽象的监听者接口 这个接口相当于-群成员(监听者)
/**
* Listener的顶级接口,为了抽象Listener而存在
*/
public interface MyListener {
void onEvent(DataEvent event);
}
定义抽象的事件接口 这个接口相当于群里面发布的通知
@Data
public abstract class DataEvent {
private String msg;
}
创建管理者的实现类,相当于具体的群(如微信群,钉钉群)
/**
* 循环调用方式的观察者(同步)
*/
@Component
public class LoopObserverImpl implements ObserverInterface<MyListener> {
//监听者的注册列表
private List<MyListener> listenerList = new ArrayList<>();
@Override
public void registerListener(MyListener listener) {
listenerList.add(listener);
}
@Override
public void removeListener(MyListener listener) {
listenerList.remove(listener);
}
@Override
public void notifyListener(DataEvent event) {
for (MyListener myListener : listenerList) {
myListener.onEvent(event);
}
}
}
创建两个event的实现类,一个是积分事件,一个是短信事件
/**
* 积分事件类
*/
public class ScoreDataEvent extends DataEvent {
private Integer score;
}
/**
* 短信事件类
*/
public class SmsDataEvent extends DataEvent {
private String phoneNum;
}
创建两个listener的实现类,一个是处理积分的,一个是处理短信的
/**
* MyListener的实现类,分数监听者
*/
@Component
public class MyScoreListener implements MyListener {
@Override
public void onEvent(DataEvent dataEvent) {
if (dataEvent instanceof ScoreDataEvent) {
//...省略业务逻辑
System.out.println("积分处理:" + dataEvent.getMsg());
}
}
}
/**
* MyListener的实现类,短信监听者
*/
@Component
public class MySmsListener implements MyListener {
@Override
public void onEvent(DataEvent dataEvent) {
if (dataEvent instanceof SmsDataEvent) {
//...省略短信处理逻辑
System.out.println("短信处理");
}
}
}
观察者模式的要素就到齐了,我们在main方法里面跑一下
public class Operator {
public static void main(String[] args) {
//通过spring的AnnotationConfigApplicationContext将com.example.demo.user.admin.design路径下的所有加了spring注解的类都扫描放入spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example.demo.user.admin.design");
//从spring容器中获取对应bean的实例
LoopObserverImpl loopObserver = context.getBean(LoopObserverImpl.class);
MyScoreListener scoreL = context.getBean(MyScoreListener.class);
MySmsListener smsL = context.getBean(MySmsListener.class);
//向观察者中注册listener
loopObserver.registerListener(scoreL);
loopObserver.registerListener(smsL);
ScoreDataEvent scoreData = new ScoreDataEvent();
scoreData.setMsg("循环同步观察者");
//发布积分事件,通知监听者
loopObserver.notifyListener(scoreData);
/*******************************************/
//从spring容器获取QueueObserverImpl观察者
QueueObserverImpl queueObserver = context.getBean(QueueObserverImpl.class);
//向观察者中注册listener
queueObserver.registerListener(scoreL);
queueObserver.registerListener(smsL);
ScoreDataEvent scoreData1 = new ScoreDataEvent();
scoreData1.setMsg("队列异步观察者");
//发布积分事件,通知监听者
queueObserver.notifyListener(scoreData1);
}
}
接下来看看下面这个新的观察者实现类和上面示例中的的观察者实现类LoopObserverImpl
有什么不同吗
/**
* 启动一个线程循环阻塞队列的观察者,可以实现解耦异步。
*/
@Component
public class QueueObserverImpl implements ObserverInterface<MyListener> {
//监听者的注册列表
private List<MyListener> listenerList = new ArrayList<>();
//创建一个大小为10的阻塞队列
private BlockingQueue<DataEvent> queue = new LinkedBlockingQueue<>(10);
//创建一个线程池
private ExecutorService executorService = new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r);
t.setName("com.kangarooking.observer.worker");
t.setDaemon(false);
return t;
});
// private ExecutorService executorService = Executors.newFixedThreadPool(1);
@Override
public void registerListener(MyListener listener) {
listenerList.add(listener);
}
@Override
public void removeListener(MyListener listener) {
listenerList.remove(listener);
}
@Override
public void notifyListener(DataEvent event) {
System.out.println("向队列放入DataMsg:" + event.getMsg());
queue.offer(event);
}
@PostConstruct
public void initObserver() {
System.out.println("初始化时启动一个线程");
executorService.submit(() -> {
while (true) {
try {
System.out.println("循环从阻塞队列里面获取数据,take是阻塞队列没有数据就会阻塞住");
DataEvent dataMsg = queue.take();
System.out.println("从阻塞队列获取到数据:" + dataMsg.getMsg());
eventNotify(dataMsg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
private void eventNotify(DataEvent event) {
System.out.println("循环所有的监听者");
for (MyListener myListener : listenerList) {
myListener.onEvent(event);
}
}
}
不同之处就是引入了阻塞队列,让通知这个操作变成异步操作,既只需要将event时间放入阻塞队列之后就可以直接返回了。不用像LoopObserverImpl
要等到listener注册表循环完毕才能返回。这样就实现了通知操作和循环listener注册表的解耦和异步。
举例说明异步实现和同步实现的区别: 同步:还是团建群的例子,假如领导是保姆型领导,通知下来任务之后可能不太放心,要挨个问,小张你准备什么表演阿,大概多久能准备好鸭。小红你呢→_→。。。 异步:假如是甩手掌柜型领导,发布完消息之后他就不管了。 上面就是同步和异步的区别,同步就是领导是个保姆,挨个问挨个了解情况之后这个事情才算完。异步就是领导发布完消息就完事儿。
开源框架的实现
同步方式
spring的发布订阅就是基于同步的观察者模式: 简单来说就是将所有的监听者注册到一个列表里面,然后当发布事件时,通过循环监听者列表,在循环里面调用每个监听者的onEvent方法,每个监听者实现的在onEvent方法里面判断传入的event是否属于当前需要的event,属于就处理该事件,反之不处理。
spring的ApplicationEventMulticaster
就是示例讲的观察者顶层接口
spring的观察者接口
ApplicationListener
就是示例代码的监听者顶层接口
spring监听者接口
在refresh
方法里面调用的registerListeners();
方法就是将所有的监听者实现类注册到观察者的注册表中
注册监听者
ApplicationEventMulticaster
的multicastEvent
方法就是上面讲的通知方法,这里就是循环监听者注册表,调用每个监听者的onApplicationEvent方法(这里的invokeListener
方法里面最终会调用到listener.onApplicationEvent(event);
)
调用监听者的onEvent方法
随便看一个onApplicationEvent
方法的实现,跟上面的例子是不是很相似
某个监听者的onEvent逻辑
异步方式
nacos中有很多地方都使用到了观察者模式,如client端和server端建立连接,发布连接事件,相关监听者做相应的处理,断开连接也是一样。
在server端接收到client端的注册请求后,会发布一个注册事件的通知
nacos-server端发布通知
在nacos-server启动的时候也是会开启一个线程做死循环,循环的去queue里面take数据,如果没有的话就会阻塞。所以死循环只有在queue里面一直有数据的时候才会一直循环,当queue里面没有数据的时候就会阻塞在queue.take();
方法处。
异步的关键代码
我们看看receiveEvent(event);
方法里面做了什么,这里就体现了框架里面设计的精妙:在上面我们自己的设计中,这里应该是需要循环调用所有的listener的onApplicationEvent
方法,但是当注册表中listener太多的时候就会出现(有些event可能会有多个listener需要处理)循环调用太慢的问题,这里使用多线程的处理方式,让这些调用并行处理,大大的提高了框架的事件处理效率。
通知监听者