基本概念

观察者模式:一种行为型设计模式,定义了对象之间的一对多依赖关系。当一个对象(主题)的状态发生改变时,所有依赖它的对象(观察者)都会自动收到通知并更新。

核心思想

  1. 主题(Subject)维护观察者(Observer)列表
  2. 主题状态变化时,主动通知所有观察者
  3. 观察者无需知道其他观察者的存在,只关注主题更新

核心结构

  • 主题(Subject):定义观察者注册/移除方法,维护观察者列表,状态变化时通知观察者。
  • 观察者(Observer):定义更新接口,接收主题通知并作出响应。
  • 具体主题(ConcreteSubject):实现主题接口,维护具体状态,状态变化时通知观察者。
  • 具体观察者(ConcreteObserver):实现观察者接口,存储与主题相关的状态,响应通知。

示例说明

场景:股票价格监控系统,当股票价格变化时,自动通知所有关注该股票的投资者

  1. 定义主题接口
1
2
3
4
5
6
7
8
9

public interface Stock {
void attach(Investor investor); // 注册观察者
void detach(Investor investor); // 移除观察者
void notifyObservers(); // 通知所有观察者
void setPrice(double price); // 更新股票价格
double getPrice(); // 获取股票价格
}

  1. 定义观察者接口,投资者
1
2
3
4
5
6

public interface Investor {
void update(Stock stock); // 接收股票价格更新通知
String getName(); // 获取投资者名称
}

  1. 具体主题实现,阿里巴巴股票主题
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

public class AlibabaStock implements Stock {
private double price;
private List<Investor> investors = new ArrayList<>();

@Override
public void attach(Investor investor) {
investors.add(investor);
System.out.println(investor.getName() + " 关注了阿里巴巴股票");
}

@Override
public void detach(Investor investor) {
investors.remove(investor);
System.out.println(investor.getName() + " 取消关注阿里巴巴股票");
}

@Override
public void notifyObservers() {
for (Investor investor : investors) {
investor.update(this); // 推模型:将主题自身传递给观察者
}
}

@Override
public void setPrice(double price) {
if (this.price != price) {
this.price = price;
System.out.println("阿里巴巴股票价格更新为: " + price);
notifyObservers(); // 价格变化时通知观察者
}
}

@Override
public double getPrice() {
return price;
}
}

  1. 具体观察者实现,普通投资者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class OrdinaryInvestor implements Investor {
private String name;

public OrdinaryInvestor(String name) {
this.name = name;
}

@Override
public void update(Stock stock) {
System.out.println(name + " 收到通知:阿里巴巴当前价格 " + stock.getPrice());
}

@Override
public String getName() {
return name;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 
public class InstitutionalInvestor implements Investor {
private String name;

public InstitutionalInvestor(String name) {
this.name = name;
}

@Override
public void update(Stock stock) {
double price = stock.getPrice();
if (price > 200) {
System.out.println(name + "(机构)注意到:阿里巴巴股价超过200,考虑卖出");
} else {
System.out.println(name + "(机构)注意到:阿里巴巴股价 " + price + ",继续持有");
}
}

@Override
public String getName() {
return name;
}
}

  1. 测试验证
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

public class StockMarket {
public static void main(String[] args) {
// 创建主题(股票)
Stock alibaba = new AlibabaStock();

// 创建观察者(投资者)
Investor investor1 = new OrdinaryInvestor("张三");
Investor investor2 = new OrdinaryInvestor("李四");
Investor institution = new InstitutionalInvestor("中投公司");

// 观察者注册到主题
alibaba.attach(investor1);
alibaba.attach(investor2);
alibaba.attach(institution);

// 更新股票价格,触发通知
alibaba.setPrice(180.5);
alibaba.setPrice(205.0); // 价格变化超过阈值,触发机构投资者特殊逻辑

// 观察者取消关注
alibaba.detach(investor1);
alibaba.setPrice(190.0);
}
}

  1. 执行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

张三 关注了阿里巴巴股票
李四 关注了阿里巴巴股票
中投公司 关注了阿里巴巴股票
阿里巴巴股票价格更新为: 180.5
张三 收到通知:阿里巴巴当前价格 180.5
李四 收到通知:阿里巴巴当前价格 180.5
中投公司(机构)注意到:阿里巴巴股价 180.5,继续持有
阿里巴巴股票价格更新为: 205.0
张三 收到通知:阿里巴巴当前价格 205.0
李四 收到通知:阿里巴巴当前价格 205.0
中投公司(机构)注意到:阿里巴巴股价超过200,考虑卖出
张三 取消关注阿里巴巴股票
阿里巴巴股票价格更新为: 190.0
李四 收到通知:阿里巴巴当前价格 190.0
中投公司(机构)注意到:阿里巴巴股价 190.0,继续持有

观察者模式有两种模型:推模型 ,拉模型

  1. 推模型(Push Model):

    • 主题主动推送数据到观察者;

    • 观察者无需主动获取,适合数据量小、通用场景,update(Stock stock) 方法传递主题对象

  2. 拉模型(Pull Model

    • 观察者主动从主题拉取数据;

    • 主题只需通知变化,观察者按需获取,适合数据量大、定制化场景;

如下示例

1
2
3
4
5
6
7
8
9
10

// 拉模型观察者接口
public interface PullInvestor {
void update(); // 仅通知变化,观察者自行获取数据
}

// 主题增加数据获取方法
public double getPrice();


JDK内置观察者实现
Java提供了java.util.Observerjava.util.Observable实现观察者模式:

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
43

// 使用JDK内置观察者实现股票监控
import java.util.Observable;
import java.util.Observer;

// 主题继承Observable
class JdkStock extends Observable {
private double price;

public void setPrice(double price) {
if (this.price != price) {
this.price = price;
setChanged(); // 标记主题已变化
notifyObservers(price); // 推模型:传递价格数据
}
}

public double getPrice() {
return price;
}
}

// 观察者实现Observer
class JdkInvestor implements Observer {
private String name;

public JdkInvestor(String name) {
this.name = name;
}

@Override
public void update(Observable o, Object arg) {
if (o instanceof JdkStock && arg instanceof Double) {
double price = (Double) arg;
System.out.println(name + " 收到JDK通知:股票价格 " + price);
}
}

public String getName() {
return name;
}
}

观察者模式定义一系列对象之间的一对多关系,当一个对象改变状态,它的依赖者都会被通知。观察者模式实现了对象之间的松耦合设计,它们可以交流,但是通常对彼此所知甚少,这里涉及到一个原则:尽量做到交互对象之间的松耦合设计

类图如下:

观察者模式

注意:JDK的Observable类存在设计缺陷(如非抽象类、线程不安全),实际开发中更推荐自定义接口实现。

总结

优点:

  • 解耦主题与观察者:双方无需知道具体实现,只依赖接口;
  • 支持广播通信:主题更新时自动通知所有观察者,无需逐个调用;
  • 扩展性好:新增观察者无需修改主题代码,符合开闭原则;

缺点:

  • 性能开销:大量观察者时,通知可能成为性能瓶颈
  • 循环依赖风险:若观察者反向修改主题状态,可能导致循环通知
  • 推模型数据冗余:主题推送的数据可能并非所有观察者都需要

使用场景

  • 消息订阅系统:新闻推送、邮件订阅、社交媒体关注,消息通知;
  • 系统状态监控:服务器状态监控(CPU、内存变化时通知监控系统),设备传感器数据变化通知(温度、湿度传感器);
  • 框架与架构设计:Spring的事件机制(ApplicationEventApplicationListener),事件总线(EventBus)模式的核心实现

注意

  • 内存泄漏:观察者注册后若未正确移除,可能导致主题持有观察者的强引用,引发内存泄漏(尤其在Android中需注意Activity销毁时移除监听)
  • 线程安全:多线程环境下需考虑注册、通知的线程安全(可通过同步或事件队列处理)
  • 推/拉模型选择:根据数据量和观察者需求选择合适的通知模式
  • 观察者排序:若通知顺序重要,需自定义观察者排序规则