在现实生活中,任何对象事务都不是孤立的。往往一个对象的改变,可能会造成相关对象的连锁反应。比如说我们日常生活中,参与公共交通的过程。红灯行绿灯行是灌输在每个人的意识中,当路口的红绿灯(对象)是红色时,所有参与这个路口的机动车、行人(对象)都会暂定,等待绿灯的指示的到来。
类似于这种案例生活中还有很多,比如我们订阅了某个微信公众号,当公众号主体推送一篇公众号推文。所有的订阅者这时候都会收到此篇文章。
程序的设计中,我们也有类似的模式。这就是接下来要说的观察者模式的使用。
定义及结构特点
观察者(Observer)模式的定义:观察者模式也称发布订阅模式(Publish-Subscribe Design Pattern) 在GoF的《设计模式》⼀书中,它的定义是, 在对象之间定义⼀个⼀对多的依赖,当⼀个对象状态改变的时候,所有依赖的对象都会⾃动收到通知。
观察者模式结构:
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
观察者模式结构图:
案例代码1:
通过模板对代码,更能直观体会设计模式的使用
- 抽象主题
/**
*
* @author jerry.feng
* 抽象目标
*/
public abstract class Subject {
protected List<Observer> observers = new ArrayList<Observer>();
//增加观察者方法
public void add(Observer observer) {
observers.add(observer);
}
//删除观察者方法
public void remove(Observer observer) {
observers.remove(observer);
}
//通知观察者方法
public abstract void notifyObserver();
}
- 具体目标
//被观察者
public class ConcreteSubject extends Subject{
@Override
public void notifyObserver() {
//遍历给所有的观察者发送消息
for (Observer observer : observers) {
observer.receiveResponse();
}
}
}
- 抽象观察者
//抽象观察者接口
public interface Observer {
//消息接收响应
void receiveResponse();
}
- 具体观察者1
public class ConcreteObsever1 implements Observer {
@Override
public void receiveResponse() {
System.out.println("观察者1-收到消息响应");
}
}
- 具体观察者2
public class ConcreteObsever2 implements Observer {
@Override
public void receiveResponse() {
System.out.println("观察者2-收到消息响应");
}
}
- 使用方
public class Client {
public static void main(String[] args) {
Subject concreteSubject = new ConcreteSubject();
//观察者1
Observer concreteObsever1 = new ConcreteObsever1();
//观察者2
Observer concreteObsever2 = new ConcreteObsever2();
//添加观察者
concreteSubject.add(concreteObsever1);
concreteSubject.add(concreteObsever2);
// 时间发生 通知所有观察者
concreteSubject.notifyObserver();
}
}
执行结果:
观察者1-收到消息响应
观察者2-收到消息响应
通过本案例可以实现简单观察者模式,在被监听者即目标对象发生改变时,通过遍历当前目标对象中维护的观察者列表,给每一个观察者发送消息。
我们不仅可以自己实现观察者模式,JDK中也为我们提供了相关的工具类, java.util.Observable
类(抽象目标)和 java.util.Observer
(抽象观察者) 接口定义了观察者模式,我们只需简单实现接口,即可实现案例中的观察者模式代码,下面通过案例2的代码来感受一下,JDK提供的工具包是如何使用。
案例代码2:
- 具体目标对象
// 被观察者角色 实现Observable 接口
public class ObservableObj extends Observable{
@Override
public void notifyObservers(Object arg) {
System.out.println("观察者数量:"+this.countObservers());
//内部状态是否发生改变
setChanged();
super.notifyObservers(arg);
}
}
- 观察者1
public class Observer1 implements Observer{
@Override
public void update(Observable o, Object arg) {
System.out.println("观察者1收到信息:"+arg.toString());
}
}
- 观察者2
public class Observer2 implements Observer{
@Override
public void update(Observable o, Object arg) {
System.out.println("观察者2收到信息:"+arg);
}
}
- 使用方
public class Client {
public static void main(String[] args) {
Observer observer1 = new Observer1();
Observer observer2 = new Observer2();
Observable observableObj = new ObservableObj();
observableObj.addObserver(observer1);
observableObj.addObserver(observer2);
observableObj.notifyObservers("msg1");
}
}
执行结果:
观察者数量:2
观察者2收到信息:msg
观察者1收到信息:msg
案例2代码结构:
通过本案例,发现JDK给我提供的观察者模式的工具类,也能方便的实现观察者模式,目标对象java.util.Observable
中,提供了
增加删除观察者的方法
重要方法:
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
- void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
- void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时, notifyObservers() 才会通知观察者。
不妨看下JDK源码是如何实现,观察者的维护,及状态位的标识维护
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
// 检验状态位
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
protected synchronized void setChanged() {
changed = true;
}
内部通过Vector<Observer>
维护观察者,这相比于ArrayList来说是线程安全的,而且每个增加删除的方法都有synchronized
关键字,这就保证了在多线程场景下的安全性。
且状态位的维护,也是同步的方式,向观察者通知时,会校验当前状态位的状态。整个过程都是同步的方式,看完整套代码,是不是觉得除了增加安全性的考虑,JDK实现的这套代码也不难。
问题思考与拓展
观察者模式很场景,主要的作用就是解耦。往大的说整个设计模式不管是创建型、结构性、还是行为型。其宗旨就是解耦,只是站的角度不同。 观察者模式常用于我们的功能设计,如订阅发布消息、邮件的发送等,还有代码层面,架构层面的解耦也会用到。
上面的案例都是同步阻塞的方式,加入业务系统中使用到同步通知的这种方式,遍历各监听者发送通知,监听者做完业务操作,势必会影响我们的整体性能。这时候可以通过异步处理的方式,在监听者部分处理,这里也会有弊端频繁的创建回收线程,也是不小的开销。
市面上也有好的观察者模式的框架,我们可以如果考虑性能方面原因,想设计一套基于架构层面更完善的发布订阅模式。Google Guava
框架中的事件总线EventBus
是个不错的选择。
EventBus
提供了同步阻塞、和异步非阻塞的API实现方式。下面通过案例实现一个业务层面的代码。
案例代码3:
这里多个了个比前两个案例多个事件的概念其实一样,和JDK中提供的API的消息(事件)是一个意思,这里案例实现这样一个小demo发送不同的事件类型消息这里模拟微信朋友圈、公众号两种消息的发送,来让不同的监听者监听,其思想还是观察者模式。
- 事件中心 提供不同事件总线API
//事件中心
public class EventBusSource {
//同步事件总线
public static final EventBus eventBus = new EventBus();
//异步事件总线
public static final EventBus asyncEventBus = new AsyncEventBus(Executors.newFixedThreadPool(4));
public static EventBus getEventBus() {
return eventBus;
}
public static EventBus getAsyncEventBus() {
return asyncEventBus;
}
}
- 目标对象(被观察者)
* @description 被观察者
* @since
*/
public class Subject {
// 获取同步事件总线
EventBus eventBus = EventBusSource.getEventBus();
public void addEventListener(List<EventListener> eventListeners) {
// 向事件总线注册监听者(观察者)
for (EventListener eventListener : eventListeners) {
eventBus.register(eventListener);
}
}
/**
* 消息发送
*
* @param domainEvent
*/
public void notifyMsg(DomainEvent domainEvent) {
eventBus.post(domainEvent);
}
}
- 事件抽象(消息)
public abstract class DomainEvent extends EventObject {
// 消息
private Object msg;
public DomainEvent(Object msg) {
super(msg);
this.msg = msg;
}
public Object getMsg() {
return msg;
}
}
- 具体事件类型1 (朋友圈事件)
public class FriendGroupEvent extends DomainEvent{
public FriendGroupEvent(Object msg) {
super(msg);
}
}
- 具体事件类型2 (公众号事件)
public class GongzhonghaoEvent extends DomainEvent{
public GongzhonghaoEvent(Object msg) {
super(msg);
}
}
- 监听者抽象(观察者)
public interface EventListener {
}
- 具体观察者1 监听公众号消息
public class WechatEventListener1 implements EventListener {
@Subscribe
public void on(GongzhonghaoEvent event) {
System.out.println("监听者1,收到了公众推送:" + event.getMsg());
}
}
- 具体观察者2 监听公众号消息
public class WechatEventListener2 implements EventListener {
@Subscribe
public void on(GongzhonghaoEvent event){
System.out.println("监听者2,收到了公众推送:" + event.getMsg());
}
}
- 具体观察者3 监听朋友圈消息
public class AEventListener implements EventListener {
@Subscribe
public void on(FriendGroupEvent friendGroupEvent) {
System.out.println("小A收到朋友圈一条新动态:" + friendGroupEvent.getMsg());
}
}
- 使用方
public class Client {
public static void main(String[] args) {
//监听者
EventListener wechatEventListener1 = new WechatEventListener1();
EventListener wechatEventListener2 = new WechatEventListener2();
EventListener aEventListener = new AEventListener();
Subject subject = new Subject();
// 添加监听者
subject.addEventListener(Arrays.asList(wechatEventListener1,wechatEventListener2,aEventListener));
//发送公众号消息
GongzhonghaoEvent event = new GongzhonghaoEvent("第一篇个人公众号");
subject.notifyMsg(event);
//发送朋友圈消息
FriendGroupEvent friendGroupEvent = new FriendGroupEvent("第一条朋友圈");
subject.notifyMsg(friendGroupEvent);
}
}
执行结果:
监听者1,收到了公众推送:第一篇个人公众号
监听者2,收到了公众推送:第一篇个人公众号
小A收到朋友圈一条新动态:第一条朋友圈
不同监听者通过不同消息类型的订阅,更加灵活的实现消息订阅通知,比我们传统的方式实现更灵活;监听者只需要在业务方法上使用注解@Subscribe
,方法参数指定具体的事件类型即可。而且可以通过异步非阻塞API的接口,实现异步事件通知。
观察者模式优缺点
优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则;
- 在一定程度上满足“开闭原则”,增加观察者,无需修改目标对象中维护观察者这部分逻辑;
- 一对多的设计模式,简化了此类问题的设计难度。
缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用;
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率(文中说了解决方案)。
观察者模式使用场景及源码使用
- 使用场景
- 适用于系统中,需要将事件的触发与响应解耦;一对多的这种对应关系,一个对象的改变,其他相关的对象都需要做出相应的响应;
- 架构层面的解耦合。
- 源码
spring事件机制,之前有写过一篇这样的文章,可以参考来看
https://blog.youkuaiyun.com/fw19940314/article/details/100010397)
✨✨ 欢迎🔔订阅个人的微信公众号 享及时博文更新