观察者 Observer

观察者模式(也称事件订阅者、监听者。)是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察”该对象的其他对象。这是对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

观察者模式的特点在于找到合适的被观察者

定义一个通知列表,将需要通知的对象放到这个通知列表中,当被观察者需要发起通知时,就会通知这个列表中的所有对象。


为什么要使用?

观察者模式的对象职责:

当一个对象改变状态时,它的所有依赖项对象都会自动得到通知和更新。

观察者模式🔎能够方便我们捕获观察对象的变化并及时做出相应的操作,同时还能提升代码扩展性。

例如,当用户🙆‍♂️(订阅者)关注的博主💃(发布者)更新时,能够及时收到更新的内容📄。使用观察者模式🔎我们可以任意扩展新的发布者或新的订阅者。

假设现在需要添加“专栏📢”这个模块,希望它能像“关注博主的用户🙆‍♂️能在博主💃更新后及时收到更新内容📄”一样。那么只需要在“发布者接口”下实现一个“专栏发布者”,就能让“订阅专栏的用户👨‍💻及时收到更新内容📄”。


模式结构

  • 发布者(Publisher)会向其他对象发送值得关注的事件。事件会在发布者自身状态改变或执行特定行为后发生。发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。当新事件发生时,发送者会遍历订阅列表并调用每个订阅者对象的通知方法。该方法是在订阅者接口中声明的。

  • 订阅者(Subscriber)接口声明了通知接口。在绝大多数情况下,该接口仅包含一个 update 更新方法。该方法可以拥有多个参数,使发布者能在更新时传递事件的详细信息。

  • 具体订阅者(Concrete Subscribers)可以执行一些操作来回应发布者的通知。所有具体订阅者类都实现了同样的接口,因此发布者不需要与具体类相耦合。订阅者通常需要一些上下文信息来正确地处理更新。因此,发布者通常会将一些上下文数据作为通知方法的参数进行传递。发布者也可将自身作为参数进行传递,使订阅者直接获取所需的数据。

  • 客户端(Client)会分别创建发布者和订阅者对象,然后为订阅者注册发布者更新。

观察者模式的类图:


模式实现

该示例使用观察者模式定义两个观察者类 DigitObserverGraphObserver 和一个发布者类 RandomNumberGenerator。实现每当发布者类 RandomNumberGenerator 执行 execute 方法生成数值时。两个观察者类 DigitObserverGraphObserver 会自动根据发布者类生成的数值分别生成“数字版数值”和“数值数量的‘*’”。

示例程序的类图

代码实现

发布者抽象类
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
35
36
37
38
39
40
41
42
package example;

import java.util.ArrayList;
import java.util.List;

/** 数字生成器发布者抽象类 */
public abstract class NumberGenerator {
/** 观察者列表 */
private List<Observer> observers = new ArrayList<>();

/**
* 添加观察者到观察者列表
* @param observer 要添加的观察者
*/
public void addObserver(Observer observer) {
observers.add(observer);
}

/**
* 删除观察者列表的观察者
* @param observer 要删除的观察者
*/
public void deleteObserver(Observer observer) {
observers.remove(observer);
}

/** 发送通知给所有观察者 */
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this);
}
}

/**
* 获取生成的数值
* @return 生成的数值
*/
public abstract int getNumber();

/** 生成数值 */
public abstract void execute();
}
发布者具体实现
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
package example;

import java.util.Random;

/** 随机数生成器发布者具体实现 */
public class RandomNumberGenerator extends NumberGenerator {
/** 随机数生成器 */
private Random random = new Random();
/** 当前数值 */
private int number = 0;

/**
* 获取生成的数值
* @return 生成的数值
*/
@Override
public int getNumber() {
return number;
}

/** 每隔1秒生成一个1~9的数值 */
@Override
public void execute() {
for (int i = 0; i < 5; i++) {
number = random.nextInt(10);
notifyObservers();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
观察者接口
1
2
3
4
5
6
7
8
9
10
package example;

/** 观察者接口 */
public interface Observer {
/**
* 数字生成后自动更新
* @param generator 数字生成器
*/
public void update(NumberGenerator generator);
}
观察者具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
package example;

/** 数字形式显示数值,具体观察者实现类 */
public class DigitObserver implements Observer {
/**
* 数字生成后自动更新
* @param generator 数字生成器
*/
@Override
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package example;

/** 图形形式显示数值,具体观察者实现类 */
public class GraphObserver implements Observer {
/**
* 数字生成后自动更新
* @param generator 数字生成器
*/
@Override
public void update(NumberGenerator generator) {
System.out.print("GraphObserver:'");
for (int i = 0; i < generator.getNumber(); i++) {
System.out.print("*");
}
System.out.println("'");
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
import example.*;

/** 测试观察者模式 */
public class Test {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer digitObserver = new DigitObserver();
GraphObserver graphObserver = new GraphObserver();
generator.addObserver(digitObserver);
generator.addObserver(graphObserver);
generator.execute();
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
DigitObserver:9
GraphObserver:'*********'
DigitObserver:0
GraphObserver:''
DigitObserver:2
GraphObserver:'**'
DigitObserver:9
GraphObserver:'*********'
DigitObserver:7
GraphObserver:'*******'

常用场景和解决方案

  • 当一个对象状态的改变需要改变其他对象时,或实际对象是事先未知的或动态变化的时。
  • 一个对象发生改变时只想要发生通知,而不需要知道接收者是谁。
  • 需要创建一种链式触发机制时。
  • 微博或微信朋友圈发送的场景。
  • 需要建立基于事件触发的场景。

模式的优缺点

优点 缺点
开闭原则。你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。 订阅者的通知顺序是随机的。
你可以在运行时建立对象之间的联系。

使用观察者模式的优势

  • 能够降低系统与系统之间的耦合性。
  • 提高代码的扩展性,能够任意引入新的订阅者类或发布者类。
  • 可以建立一套基于目标对象特定操作或数据的触发机制。

使用观察者模式的劣势

  • 增加代码的理解难度。
  • 降低了系统的性能。

拓展知识

责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

  • 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
  • 命令在发送者和请求者之间建立单向连接。
  • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
  • 观察者允许接收者动态地订阅或取消接收请求。


🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1