封面来源:观察者设计模式 (refactoringguru.cn) ,如有侵权,请联系删除。
Java 设计模式学习网站:Java设计模式:23种设计模式全面解析(超级详细)
菜鸟教程:观察者模式
1. 模式的定义与特点
相信大家一定遇到过这样的场景,实体 A 与实体 B 之间存在某种联系,当对实体 A 进行删除时,需要同时删除某些实体 B。要完成这样的需求也很简单,只需要知道实体 A 与实体 B 之间的联系即可。比如,实体 A 的主键 ID 对应了实体 B 中的某个字段,那么就可以在删除实体 A 时获取其主键,然后利用这个值删除对应的实体 B 即可。
像上述这样的需求,首先想到的就是将这些逻辑紧凑地写在一个方法里,但这会使得代码耦合度较高。假如又来了个实体 C,而实体 C 与实体 B 之间又有某些关系,同样需要在删除实体 B 的数据时删除对应的实体 C,此时若在原方法的基础上进行修改,那么产生 BUG 的概率也会增加。
因此最好让这些存在关系的实体之间增加一种触发响应机制,以达到解耦的目的。
定义
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布 - 订阅模式、模型 - 视图模式,它是对象行为型模式。
优点
1、降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
2、目标与观察者之间建立了一套触发机制。
缺点
1、目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
2、当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
3、没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
2. 适用场景
在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式以松耦合的方式联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
【观察者模式】主要解决一个对象状态改变后需要通知其他对象的问题,而且要考虑到易用和低耦合,保证高度的协作。
【观察者模式】适用于以下场景:
1、对象间存在一对多关系,一个对象的状态发生改变会影响其他对象;
2、当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用;
3、实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播;
4、多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。就像文章开头所举的例子,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象等。
3. 模式的结构
注意: 实现观察者模式时具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
【观察者模式】的主要角色如下:
1、抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
2、具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象(就是被观察者观察的目标,可以理解为被观察者)。
3、抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
4、具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
【观察者模式】结构图:
4. 代码的实现
有了结构图,代码实现还不简单吗?
首先编写观察者接口:
1 2 3 4 5 6 7 8 9 10 11 12 public interface Observer { void response () ; }
根据这个接口,编写两个具体的观察者:
1 2 3 4 5 6 7 8 9 10 11 12 public class ConcreteObserverOne implements Observer { @Override public void response () { System.out.println("观察者 1 号做出了响应..." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 public class ConcreteObserverTwo implements Observer { @Override public void response () { System.out.println("观察者 2 号做出了响应..." ); } }
然后再编写抽象主题,或者说被观察者(我更喜欢后者,观察者、被观察者,不是更好理解?),在这里面需要维护一个 ArrayList
对象来存放观察者们:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public abstract class Subject { protected List<Observer> observers = new ArrayList <>(); public void add (Observer observer) { observers.add(observer); } public void remove (Observer observer) { observers.remove(observer); } public abstract void notifyObserver () ; }
根据这个抽象类,完成具体主题(或者说具体被观察者)的编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ConcreteSubject extends Subject { @Override public void notifyObserver () { for (Object obs : observers) { ((Observer) obs).response(); } } }
最后编写一个测试方法来测试一下:
1 2 3 4 5 6 7 8 9 10 @Test public void testObserverPattern () { ConcreteSubject subject = new ConcreteSubject (); subject.add(new ConcreteObserverOne ()); subject.add(new ConcreteObserverTwo ()); subject.notifyObserver(); }
运行测试类后,控制台打印出:
观察者 1 号做出了响应...
观察者 2 号做出了响应...
使用的注意事项
1、避免循环引用;
2、由于维护了一个集合来存放观察者,因此顺序执行时,错误的观察者可能会导致通知阻塞,最好采用异步的方式。
5. 模式的拓展
其实在很多框架中都使用到了观察者模式,甚至 JDK 中也定义了观察者模式。
对于框架来说,比如:
1、在 Spring 框架中,ApplicationListener
就是采用观察者模式来处理的,与之对应的是 ApplicationEventMulticaster
作为主题实现对观察者的添加、删除、通知等操作。
2、Google Guava 的事件处理机制是采用 EventBus,而其实现也是采用观察者模式。
3、说到 EventBus 就不得不说由 GreenRobot 组织贡献的 Android 事件发布 / 订阅轻量级框架 EventBus,其实现也是采用的观察者模式。
EventBus 的执行流程:
而在 JDK 中,java.util
包下的 Observable
类和 Observer
接口定义了观察者模式,只需要实现他们的子类就可以编写观察者模式实例。
5.1 JDK 中的定义(已过时)
继承 Observable
抽象类,完成数值被观察者的编写。其内部定义了一个方法,当传入一个 int
类型的参数后,会改变成员变量 num
的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import lombok.Getter;import java.util.Observable;@Getter public class NumberObservable extends Observable { private int num = 1 ; public void changeNum (int data) { this .num = data; this .setChanged(); this .notifyObservers(data); } }
再实现 Observer
接口,完成数值观察者的编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.Observable;import java.util.Observer;public class NumberObserver implements Observer { @Override public void update (Observable o, Object arg) { NumberObservable observable = (NumberObservable) o; System.out.println("数值改变为:" + observable.getNum() + "(使用被观察者对象获取)" ); System.out.println("数值改变为:" + arg + "(使用方法参数获取)" ); } }
编写一个测试方法测试一下,看看调用被观察者的 changeNum()
方法时会不会执行观察者中的 update()
方法:
1 2 3 4 5 6 7 8 9 10 @Test public void testJdkObserver () { NumberObservable observable = new NumberObservable (); observable.addObserver(new NumberObserver ()); observable.changeNum(1 ); observable.changeNum(100 ); }
运行这个测试方法后,控制台打印出:
数值改变为:1(使用被观察者对象获取)
数值改变为:1(使用方法参数获取)
数值改变为:100(使用被观察者对象获取)
数值改变为:100(使用方法参数获取)
Observable 类中三个方法的解析
首先需要说明的是:Observable
中维护了一个 Vector
对象,用于保存所有要通知的观察者对象。
1、addObserver(Observer o)
方法:用于将新的观察者对象添加到 Vector 中;
2、notifyObservers(Object arg)
方法:调用 Vector 中的所有观察者对象的 update()
方法,通知它们被观察者的数据发生了改变。一般来说,越晚添加进 Vector 的观察者越先得到通知,为什么这么说呢?看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 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); }
3、setChange()
方法:用来设置一个 boolean
类型的内部标志位,注明目标对象发生了变化。当它为 true
时,notifyObservers()
才会通知观察者。
5.2 JDK 中的定义
从 JDK9 开始,Observable
与 Observer
被标记为过时。原因有多种:
Observable
未实现序列化接口 Serializable
,其内部成员变量也都是私有的,子类不能对他们进行修改;
Observable
不是线程安全的、事件通知无序、状态变化与事件也不是一一对应的;
支持的事件模型十分有限,不能告知哪些内容发生了改变。
对于更丰富的事件模型,考虑使用 java.beans
包;对于在线程之间进行可靠和有序的消息传递,考虑使用 java.util.concurrent
包中提供的并发数据结构;对于响应流式编程,可以参阅 java.util.concurrent.Flow
。
以 java.beans
包中的 PropertyChangeSupport
和 PropertyChangeListener
为例,它们提供了更丰富的线程模型,能够告知哪些内容发生了改变。
在事件源中声明 PropertyChangeSupport
类型的成员变量,提供添加、删除事件监听器的公共方法,在属性发生变化时,向监听器报告属性的变化信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Getter public class EventSource { private int num = 1 ; private final PropertyChangeSupport listeners = new PropertyChangeSupport (this ); public void addListener (PropertyChangeListener listener) { listeners.addPropertyChangeListener(listener); } public void removeListener (PropertyChangeListener listener) { listeners.removePropertyChangeListener(listener); } public void changeNum (int data) { listeners.firePropertyChange("num" , this .num, data); this .num = data; } }
监听器实现 PropertyChangeListener
接口,重写 propertyChange()
方法,该方法的 PropertyChangeEvent
参数包含了事件源中属性的变化信息:
1 2 3 4 5 6 7 public class EventListener implements PropertyChangeListener { @Override public void propertyChange (PropertyChangeEvent evt) { System.out.println("变化的属性是: " + evt.getPropertyName()); System.out.println("值从: " + evt.getOldValue() + " 变化为: " + evt.getNewValue()); } }
使用时设置事件源的监听器,然后触发属性变化即可:
1 2 3 4 5 6 7 8 9 10 @Test public void testJdkObserver () { EventSource observable = new EventSource (); observable.addListener(new EventListener ()); observable.changeNum(0 ); observable.changeNum(100 ); }
变化的属性是: num
值从: 1 变化为: 0
变化的属性是: num
值从: 0 变化为: 100
5.3 Google Guava EventBus
导入 Guava 的依赖:
1 2 3 4 5 <dependency > <groupId > com.google.guava</groupId > <artifactId > guava</artifactId > <version > 31.1-jre</version > </dependency >
先编写一个成功的事件类:
1 2 3 4 5 6 7 8 9 10 @Getter @Setter @AllArgsConstructor public class SuccessfulEvent { private String msg; }
然后编写成功事件的监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 import com.google.common.eventbus.Subscribe;public class SuccessfulListener { @Subscribe public void onSuccessful (SuccessfulEvent event) { System.out.println(event.getMsg()); } }
注意: 事件监听方法得使用 Guava 的 @Subscribe
注解进行标注。
最后编写一个测试方法来测试一下:
1 2 3 4 5 6 7 8 @Test public void testGuavaEventBus () { final EventBus eventBus = new EventBus (); eventBus.register(new SuccessfulListener ()); eventBus.post(new SuccessfulEvent ("监听到成功事件" )); }
当运行测试方法后,控制台打印出:
监听到成功事件
5.4 GreenRobot EventBus
相比于 Guava 的 EventBus,我更喜欢 GreenRobot 的 EventBus,因为在 GreenRobot EventBus 中可以通过调用静态方法来完成监听器的注册和事件的发送,不需要额外 new
一个对象。
除此之外,Guava 中的 @Subscribe
注解和 EventBus
类都被 @Beta
注解所标记,表明它们都处于 Beta 测试版本,而非正式版。 现已成为正式 API。
同样,先导入 GreenRobot EventBus 的依赖:
1 2 3 4 5 <dependency > <groupId > org.greenrobot</groupId > <artifactId > eventbus-java</artifactId > <version > 3.3.1</version > </dependency >
有了成功事件,再编写一个失败事件类:
1 2 3 4 5 6 7 8 9 10 @Getter @Setter @AllArgsConstructor public class FailureEvent { private String failureMsg; }
为其编写失败监听器:
1 2 3 4 5 6 7 8 9 10 11 12 import org.greenrobot.eventbus.Subscribe;public class FailureListener { @Subscribe public void onFailure (FailureEvent event) { System.out.println(event.getFailureMsg()); } }
同样的编写测试方法测试一下:
1 2 3 4 5 6 7 @Test public void testGreenrobotEvent () { org.greenrobot.eventbus.EventBus.getDefault().register(new FailureListener ()); org.greenrobot.eventbus.EventBus.getDefault().post(new FailureEvent ("监听到失败事件" )); }
当我们运行测试方法后,控制台打印出:
监听到失败事件
5.5 项目中的使用
无论是 Google Guava EventBus 还是 GreenRobot EventBus 都提供了一种十分简单的方式来实现观察者模式,让事件的监听变得容易,那怎么在实际项目中(Spring Boot 工程中)使用它们呢?
通过上述的代码不难发现实现事件的监听需要事件监听器的注册和事件的发送。
监听器注册需要传入监听器对象,那么可以自定义一个 @EventHandler
注解,这个注解只能作用于类上,它作为一个标记,让被这个注解标记的类可以被 SpringBoot 扫描到,最终将监听器注册到 EventBus 中。
而对于 EventBus 而言,使其交由 Spring 托管,需要发送事件时,采用依赖注入的方式注入到目标类中:
1 2 @Autowired private EventBus eventBus;
然后使用 eventBus.post(事件对象);
完成事件的发送,而开发者只需要关注事件对象和事件监听类的编写即可。
代码示例
监听器标记注解:
1 2 3 4 5 @Component @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface EventHandler {}
监听器注册类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Component public class EventBusRegister { @Autowired private ConfigurableListableBeanFactory beanFactory; private final EventBus syncEventBus = new EventBus (); private final AsyncEventBus asyncEventBus = new AsyncEventBus (Executors.newCachedThreadPool()); public void postSync (Object event) { syncEventBus.post(event); } public void postAsync (Object event) { asyncEventBus.post(event); } @PostConstruct public void init () { List<Object> beans = new ArrayList <>(beanFactory.getBeansWithAnnotation(EventHandler.class).values()); beans.forEach(i -> { syncEventBus.register(i); asyncEventBus.register(i); }); } }
现在假设有一订单创建事件:
1 2 3 4 5 @ToString @AllArgsConstructor public class OrderCreateEvent { private final Long orderId; }
当订单创建后,发送订单创建事件:
1 2 3 4 5 6 7 8 9 10 @Service public class OrderService { @Autowired private EventBusRegister eventBusRegister; public void createOrder () { eventBusRegister.postAsync(new OrderCreateEvent (1L )); } }
监听器监听到事件后,打印事件信息:
1 2 3 4 5 6 7 8 @EventHandler public class OrderListener { @Subscribe public void created (OrderCreateEvent orderCreateEvent) { System.out.println(orderCreateEvent); } }
测试创建订单后发送的事件是否能被监听器监听到:
1 2 3 4 5 6 7 @Autowired private OrderService orderService;@Test public void testEventBus () { orderService.createOrder(); }
运行测试方法后,控制台打印出:
OrderCreateEvent(orderId=1)