什么是观察者模式?
观察者模式(Observer Pattern)是一种行为型设计模式,也叫"发布-订阅模式"。它的核心思想是:当一个对象(被观察者)的状态发生变化时,会自动通知所有依赖它的对象(观察者),无需主动轮询或回调。
生活比喻
想象你订阅了一个YouTube频道。当频道发布新视频时,你不需要每隔几分钟刷新一次页面——YouTube会自动推送通知给你。YouTube是被观察者(Subject),你是观察者(Observer)。你只订阅一次,之后有新内容就会自动收到通知。
观察者模式结构:
┌─────────────────────────────────────────────────────────────┐
│ Subject(被观察者) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ observers: List<Observer> │ │
│ │ attach(observer) → 添加观察者 │ │
│ │ detach(observer) → 移除观察者 │ │
│ │ notify() → 通知所有观察者 │ │
│ └─────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│ notify
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│Observer A │ │Observer B │ │Observer C │
│update() │ │update() │ │update() │
└───────────┘ └───────────┘ └───────────┘
完整实现
1. 定义观察者接口
// 观察者接口 - 定义收到通知时的行为
public interface Observer {
void update(String message); // 收到更新时调用
}
2. 定义被观察者
import java.util.ArrayList;
import java.util.List;
// 被观察者 - 当状态变化时通知所有观察者
public class Subject {
private String state;
private List observers = new ArrayList<>();
// 添加观察者
public void attach(Observer observer) {
observers.add(observer);
}
// 移除观察者
public void detach(Observer observer) {
observers.remove(observer);
}
// 通知所有观察者
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this.state);
}
}
// 设置状态(这会触发通知)
public void setState(String state) {
this.state = state;
System.out.println("[Subject] 状态变为: " + state);
notifyObservers(); // 状态变化,自动通知
}
public String getState() {
return state;
}
}
3. 创建具体观察者
// 具体观察者A
class ConcreteObserverA implements Observer {
private String name;
public ConcreteObserverA(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println("[" + name + "] 收到通知: " + message);
}
}
// 具体观察者B
class ConcreteObserverB implements Observer {
private String name;
public ConcreteObserverB(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println("[" + name + "] 处理消息: " + message.toUpperCase());
}
}
4. 使用
public class ObserverDemo {
public static void main(String[] args) {
Subject subject = new Subject();
Observer alice = new ConcreteObserverA("Alice");
Observer bob = new ConcreteObserverA("Bob");
Observer charlie = new ConcreteObserverB("Charlie");
System.out.println("===== 添加三个观察者 =====");
subject.attach(alice);
subject.attach(bob);
subject.attach(charlie);
System.out.println("\n===== 第一次状态更新 =====");
subject.setState("START");
System.out.println("\n===== Bob取消订阅 =====");
subject.detach(bob);
System.out.println("\n===== 第二次状态更新 =====");
subject.setState("COMPLETE");
}
}
实战案例:股票行情系统
import java.util.*;
// 股票(被观察者)
class Stock {
private String symbol;
private double price;
private List investors = new ArrayList<>();
public Stock(String symbol, double price) {
this.symbol = symbol;
this.price = price;
}
public void addInvestor(Investor investor) {
investors.add(investor);
}
public void removeInvestor(Investor investor) {
investors.remove(investor);
}
public void setPrice(double newPrice) {
double oldPrice = this.price;
this.price = newPrice;
System.out.println("[" + symbol + "] 价格变动: $" + oldPrice + " → $" + newPrice);
notifyInvestors();
}
private void notifyInvestors() {
for (Investor investor : investors) {
investor.update(this);
}
}
public String getSymbol() { return symbol; }
public double getPrice() { return price; }
}
// 投资者(观察者)
interface Investor {
void update(Stock stock);
}
// 个人投资者
class IndividualInvestor implements Investor {
private String name;
private double threshold;
public IndividualInvestor(String name, double threshold) {
this.name = name;
this.threshold = threshold; // 价格变化超过这个阈值就买入/卖出
}
@Override
public void update(Stock stock) {
System.out.println("[" + name + "] 收到更新 - " + stock.getSymbol() +
" 当前价格: $" + stock.getPrice());
}
}
// 机构投资者
class InstitutionalInvestor implements Investor {
private String name;
public InstitutionalInvestor(String name) {
this.name = name;
}
@Override
public void update(Stock stock) {
System.out.println("[" + name + "] 机构交易系统收到更新");
if (stock.getPrice() < 150) {
System.out.println(" → 建议买入 " + stock.getSymbol());
} else if (stock.getPrice() > 200) {
System.out.println(" → 建议卖出 " + stock.getSymbol());
}
}
}
public class StockMarketDemo {
public static void main(String[] args) {
Stock apple = new Stock("AAPL", 175.50);
Investor investor1 = new IndividualInvestor("张三", 5.0);
Investor investor2 = new IndividualInvestor("李四", 10.0);
Investor investor3 = new InstitutionalInvestor("高盛");
apple.addInvestor(investor1);
apple.addInvestor(investor2);
apple.addInvestor(investor3);
System.out.println("===== 第一次价格变动 =====");
apple.setPrice(172.30);
System.out.println("\n===== 第二次价格变动 =====");
apple.setPrice(205.00);
}
}
Java内置的观察者模式
Java早期提供了Observable类和Observer接口,但它们已被标记为@Deprecated(Java 9之后)。现在推荐自己实现或使用第三方库如Guava的EventBus。
观察者模式的优缺点
优点
松耦合:被观察者不需要知道观察者的具体类型
动态订阅:可以随时添加/移除观察者
自动通知:状态变化自动推送
缺点
内存泄漏:忘记取消订阅会导致泄漏
通知顺序:观察者收到通知的顺序不确定
循环依赖:复杂的观察关系可能导致循环
应用场景
| 场景 | 被观察者 | 观察者 |
|---|---|---|
| 消息推送 | 消息中心 | 订阅者 |
| 股票行情 | 股票 | 投资者 |
| GUI事件 | 按钮 | 点击监听器 |
| 数据绑定 | 数据模型 | UI组件 |
| 日志系统 | Logger | 日志处理器 |
注意事项
内存泄漏风险!
如果观察者不再使用但没有取消订阅,被观察者仍会持有观察者的引用,导致内存泄漏。解决方案:
- 在不需要时调用detach()
- 使用WeakHashMap存储观察者
- 使用生命周期管理(如Android的onDestroy)
小结
- 观察者模式实现一对多的依赖关系
- 当被观察者状态变化时,自动通知所有观察者
- 可以动态添加/移除观察者
- 遵循松耦合原则,被观察者不需要知道观察者的具体类型
- 注意内存泄漏问题,及时取消订阅