所谓观察者模式,就是一旦观察的对象发生了改变,立马通知所有观察者做出相应的改变。是不是和发布订阅很像,因此观察者模式又叫发布订阅模式。
观察者使用范围十分广泛,比如我们关注的微信公众号有新内容,订阅的用户会有提示;又比如你在起点上观看的小说有更新,也会有消息提醒。
而一般的观察者模式由观察目标、抽象观察者、具体观察者构成。其中观察目标就可以理解为用户关注的公众号。
这里的初版代码我就直接使用菜鸟教程的代码了。
/**
* 观察目标类,观察目标类必须要知道所有订阅自己的观察者,不然无法发送更新通知给观察者们
*
* @author zxb 2023/6/22 9:20
*/
public class Subject {
private List<Observe> observes = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
// 更新后通知所有观察者
notifyAllObserve();
}
/**
* 添加观察者
*
* @param observe 观察者
*/
public void attach(Observe observe) {
observes.add(observe);
}
/**
* 删除观察者
*
* @param observe 观察者
*/
public void remove(Observe observe) {
observes.remove(observe);
}
/**
* 通知所有观察者,观察目标属性发生变更
*/
public void notifyAllObserve() {
for (Observe observe : observes) {
observe.update();
}
}
}
观察目标核心就是三个方法,一个观察者感兴趣的状态,其它可以根据具体业务扩充。
/**
* 抽象观察者类
*
* @author zxb 2023/6/22 9:22
*/
public abstract class Observe {
protected Subject subject;
public abstract void update();
}
抽象观察者就是要提供一个更新方法给观察目标调用。
这里有三个具体的观察者实现类。
package org.example.observe;
/**
* @author zxb 2023/6/22 9:29
*/
public class BinaryObserver extends Observe {
public BinaryObserver(Subject subject) {
this.subject = subject;
subject.attach(this);
}
@Override
public void update() {
System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(subject.getState()));
}
}
package org.example.observe;
/**
* @author zxb 2023/6/22 9:29
*/
public class OctalObserver extends Observe {
public OctalObserver(Subject subject) {
this.subject = subject;
subject.attach(this);
}
@Override
public void update() {
System.out.println("OctalObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toOctalString(subject.getState()));
}
}
package org.example.observe;
/**
* @author zxb 2023/6/22 9:29
*/
public class HexObserver extends Observe {
public HexObserver(Subject subject) {
this.subject = subject;
subject.attach(this);
}
@Override
public void update() {
System.out.println("HexObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toHexString(subject.getState()));
}
}
上面三个观察者分别感知观察对象的 state 状态,一但发生变化,即打印更新的内容,分别是二进制、八进制、十六进制打印。
package org.example.observe;
/**
* @author zxb 2023/6/22 9:37
*/
public class ObserveTest1 {
public static void main(String[] args) {
// 创建被感知的对象
Subject subject = new Subject();
// 创建观察者们
new BinaryObserver(subject);
new OctalObserver(subject);
new HexObserver(subject);
// 观察目标发生更新
subject.setState(1);
subject.setState(2);
}
}
执行结果如下:
可以看到,一但观察目标发生更新,所有观察者都会做出相应的变化。这就是观察者模式的提现。
在很多观察者模式的博客中,都会提到观察者模式容易出现循环依赖的情况,那么下面就来复现下循环依赖。
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
那么将上面的代码稍微改一下。
初版代码实现中抽象观察者关联了观察目标的引用,该引用被各自具体观察者的构造方法入参赋值。
那么如果错误编码,导致在具体观察者的构造方法中直接实例化观察对象,而不是通过入参赋值。并且,在观察对象的构造方法中实例化具体的观察者,然后调用添加观察者方法向容器中添加观察者,这样就会出现循环依赖问题。
循环依赖代码如下:
package org.example.observe_circle_rely;
import java.util.ArrayList;
import java.util.List;
/**
* @author zxb 2023/6/22 9:20
*/
public class Subject {
public Subject() {
// 直接实例化观察者,并将观察者加入观察者容器。
Observe binaryObserver = new BinaryObserver();
Observe octalObserver = new OctalObserver();
Observe hexObserver = new HexObserver();
attach(binaryObserver);
attach(octalObserver);
attach(hexObserver);
}
private List<Observe> observes = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObserve();
}
/**
* 添加观察者
*
* @param observe 观察者
*/
public void attach(Observe observe) {
observes.add(observe);
}
/**
* 删除观察者
*
* @param observe 观察者
*/
public void remove(Observe observe) {
observes.remove(observe);
}
public void notifyAllObserve() {
for (Observe observe : observes) {
observe.update();
}
}
}
抽象观察者不变。和初版代码实现一致。
package org.example.observe_circle_rely;
/**
* @author zxb 2023/6/22 9:29
*/
public class BinaryObserver extends Observe {
public BinaryObserver() {
// 在构造方法中实例化观察目标。并在 update 方法中试图获取观察目标的值
subject = new Subject();
}
@Override
public void update() {
System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(subject.getState()));
}
}
其余具体观察者和 BinaryObserver 一致,都是在构造方法中实例化了观察目标,就不将代码贴出来了。
package org.example.observe_circle_rely;
/**
* @author zxb 2023/6/22 9:37
*/
public class ObserveTest1 {
public static void main(String[] args) {
// 创建被感知的对象
Subject subject = new Subject();
// 由于向观察者容器中添加观察者对象的操作在 Subject 构造方法中做了,因此不用显示调用
subject.setState(1);
}
}
这就是当观察者和观察目标直接互相严重依赖导致的问题。因此在使用观察者模式时,要注意这个问题。
初版代码能够实现基本的观察者模式,但是在单元测试中,我们需要去手动创建 N 多个观察者,稍显麻烦。
并且,在抽象观察者中,关联了观察对象的引用,其实并不需要,我们只需要感知观察对象状态变更的结果就行。
且现在都是基于 Spring 环境开发,我们可以利用 Spring 的 DI 特性来简化代码。
具体代码如下:
package com.example.just_boot.observe;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 观察目标类,观察目标类必须要知道所有订阅自己的观察者,不然无法发送更新通知给观察者们
*
* @author zxb 2023/6/22 9:20
*/
@Component
public class Subject implements ApplicationContextAware {
private List<Observe> observes = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
// 更新后通知所有观察者
notifyAllObserve();
}
/**
* 添加观察者
*
* @param observe 观察者
*/
public void attach(Observe observe) {
observes.add(observe);
}
/**
* 删除观察者
*
* @param observe 观察者
*/
public void remove(Observe observe) {
observes.remove(observe);
}
/**
* 通知所有观察者,观察目标属性发生变更
*/
public void notifyAllObserve() {
for (Observe observe : observes) {
// 将更新后的结果告知观察者们
observe.update(getState());
}
}
/**
* 从容器中获取所有观察者,在 IOC 容器启动时将观察者放入观察者容器。
*
* @param applicationContext IOC 容器
* @throws BeansException exception
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
String[] beanNamesForType = applicationContext.getBeanNamesForType(Observe.class);
if (ArrayUtils.isNotEmpty(beanNamesForType)) {
for (String beanName : beanNamesForType) {
Observe bean = (Observe) applicationContext.getBean(beanName);
attach(bean);
}
}
}
}
抽象观察者不再关联观察结果对象,而是将观察结果变更的属性通过入参传入。
package com.example.just_boot.observe;
/**
* 抽象观察者类
*
* @author zxb 2023/6/22 9:22
*/
public abstract class Observe {
public abstract void update(Integer result);
}
package com.example.just_boot.observe;
import org.springframework.stereotype.Component;
/**
* @author zxb 2023/6/22 9:29
*/
@Component
public class BinaryObserver extends Observe {
@Override
public void update(Integer result) {
System.out.println("BinaryObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
}
}
package com.example.just_boot.observe;
import org.springframework.stereotype.Component;
/**
* @author zxb 2023/6/22 9:29
*/
@Component
public class OctalObserver extends Observe {
@Override
public void update(Integer result) {
System.out.println("OctalObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
}
}
package com.example.just_boot.observe;
import org.springframework.stereotype.Component;
/**
* @author zxb 2023/6/22 9:29
*/
@Component
public class HexObserver extends Observe {
@Override
public void update(Integer result) {
System.out.println("HexObserver 感知到观察的对象发生更新,更新的内容为 : " + Integer.toBinaryString(result));
}
}
package com.example.just_boot.controller;
import com.example.just_boot.observe.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author zxb 2023/6/22 13:09
*/
@RestController
@RequestMapping(path = "/observe")
public class ObserveController {
@Resource
private Subject subject;
@GetMapping(path = "/")
public String testUpdate() {
subject.setState(15);
return "success";
}
}
访问 http://localhost:8080/observe/, 页面返回 success。控制台打印如下:
通过上面的迭代更新,观察者模式的代码已初显威力,但是也还有优化的地方。比如观察者对象也可以定义一个接口,定义通用的方法规范,然后定义实现类进行扩充。
设计模式代码风格多变,但是核心思想不变。
观察者模式代码的核心思想就是:
● 定义观察目标对象,在该对象中定义:添加观察者方法、删除观察者方法、通知所有观察者方法、更新自身状态的方法(观察者观察的属性)。此外,该对象中还需要有一个观察者容器,用来存放所有观察此对象的观察者。这个观察者容器必须要有,不然当观察目标发生变化,无法通知到所有观察此对象的观察者们。
● 定义抽象观察者类,该对象中一般定义一个更新方法,用于观察目标自身属性发生更新,调用此更新方法通知观察者。
● 定义具体观察者类,这里具体的观察者类一般有多个,他们在更新方法中实现当观察目标属性变更时做出的具体逻辑。