1. 认识“观察者”模式
“观察者”(Observer)模式是最知名、最基础的设计模式之一。它描述了这样一种情形:一个对象(被观察者,subject)在状态发生变化时,通知已订阅这些变化的其他对象(观察者,observers)。
通俗地说:我们有一个 Telegram 频道(被观察对象),还有“订阅者”(观察者)。每当发布新帖子,频道就会通知所有订阅者,而订阅者再决定如何处理——阅读、忽略或取消订阅。
在编程中,该模式允许在对象之间不直接耦合的前提下,自动通知感兴趣的对象有关事件或状态变化。 这对于构建灵活、可扩展且易维护的系统至关重要。
观察者模式出现在哪里?
- 在图形界面(Swing、AWT、JavaFX)中——事件监听器。
- 在响应式库(RxJava、Project Reactor)中.
- 在业务逻辑中:对模型状态变化作出响应。
- 在游戏引擎中(碰撞、胜利、失败等事件)。
- 只要需要把“发生了什么”和“如何处理”解耦的地方。
与 Java 事件/监听器模型的关系
实际上,Java 的事件模型就是基于“观察者”的。当你编写 button.addActionListener(listener); 时,您就在实现该模式:
- 被观察者——按钮(或其他组件)。
- 观察者——你的监听器,实现了方法 actionPerformed()。
- 事件——用户点击、鼠标悬停等。
- 通知——组件调用 actionPerformed()。
这就是 Observer 的经典实现!
2. 观察者模式的经典实现
我们用自己的类来实现这个模式——不依赖 Swing 或 AWT,这样更容易看出并没有什么魔法。
模式的基本元素
- Observable (Subject)——被观察对象。保存观察者列表,并在变化时通知它们。
- Observer——观察者接口,通常包含方法 update()。
示例:温度计与空调
观察者接口
public interface TemperatureObserver {
void temperatureChanged(int newTemperature);
}
“温度计”类(被观察者)
import java.util.*;
public class Thermometer {
private int temperature;
private final List<TemperatureObserver> observers = new ArrayList<>();
public void addObserver(TemperatureObserver observer) {
observers.add(observer);
}
public void removeObserver(TemperatureObserver observer) {
observers.remove(observer);
}
public void setTemperature(int newTemperature) {
if (this.temperature != newTemperature) {
this.temperature = newTemperature;
notifyObservers();
}
}
private void notifyObservers() {
for (TemperatureObserver observer : observers) {
observer.temperatureChanged(temperature);
}
}
}
观察者示例——“空调”
public class AirConditioner implements TemperatureObserver {
@Override
public void temperatureChanged(int newTemperature) {
if (newTemperature > 25) {
System.out.println("空调已开启!很热:" + newTemperature + "°C");
} else {
System.out.println("空调已关闭。温度:" + newTemperature + "°C");
}
}
}
用法
public class Main {
public static void main(String[] args) {
Thermometer thermometer = new Thermometer();
AirConditioner conditioner = new AirConditioner();
thermometer.addObserver(conditioner);
thermometer.setTemperature(22); // 空调已关闭。温度:22°C
thermometer.setTemperature(28); // 空调已开启!很热:28°C
}
}
就是这么简单! 你可以再添加一百个观察者——每当温度变化,他们都会收到通知。
模式的示意图
flowchart LR
T["温度计(Observable)"] -- 通知 --> AC["空调(Observer)"]
T -- 通知 --> L["记录器(Observer)"]
T -- 通知 --> Alarm["报警器(Observer)"]
现代细节:过时的 Observable 与新方法
在 Java 标准库中,曾有 java.util.Observable 和 java.util.Observer,但从 Java 9 起它们被标记为已弃用(deprecated)。原因是灵活性不足(例如,Observable 是类而不是接口,因此更难从其他类继承)。
现代做法是设计自定义的监听器接口以及订阅/取消订阅逻辑(如上例)。这样更灵活、更安全,也更贴合实际需求。
3. 示例:带订阅者的迷你应用
我们实现一个“点击计数器”,并允许订阅数值变化。
监听器接口
public interface CounterListener {
void counterChanged(int newValue);
}
计数器类
import java.util.*;
public class Counter {
private int value = 0;
private final List<CounterListener> listeners = new ArrayList<>();
public void addCounterListener(CounterListener l) {
listeners.add(l);
}
public void removeCounterListener(CounterListener l) {
listeners.remove(l);
}
public void increment() {
value++;
notifyListeners();
}
private void notifyListeners() {
for (CounterListener l : listeners) {
l.counterChanged(value);
}
}
public int getValue() {
return value;
}
}
监听器:打印消息
public class ConsoleCounterListener implements CounterListener {
@Override
public void counterChanged(int newValue) {
System.out.println("计数器发生变化:" + newValue);
}
}
用法
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
counter.addCounterListener(new ConsoleCounterListener());
counter.increment(); // 计数器发生变化:1
counter.increment(); // 计数器发生变化:2
}
}
4. 实用细节
现代替代方案与扩展
在真实项目中,常用匿名类或 lambda 表达式来订阅: counter.addCounterListener(newValue -> System.out.println("新值:" + newValue));
(要这样做,接口必须是函数式接口——只包含一个抽象方法。)
响应式库(RxJava、Project Reactor)也很流行,它们以事件流、过滤、异步等能力来实现“观察者”。要理解本质,上面讲解的经典方案已经足够。
观察者模式的常见应用
- 数据模型。 模型发生变化(任务、商品、用户等列表)时通知视图进行更新。
- 日志记录。 日志订阅者在整个系统中对事件作出反应。
- 通知。 状态变化时——发送电子邮件、推送通知、Telegram 消息。
- 游戏。 生命值变化、敌人出现、关卡完成。
- 多线程。 一个线程发布事件,其他线程进行响应。
5. 实现观察者模式的常见错误
错误 1:忘记移除监听器。 如果监听器不再需要却没有被移除,它仍会继续接收通知。在长生命周期的应用中这可能导致内存泄漏。
错误 2:处理器中进行耗时或阻塞操作。 如果处理器执行繁重工作(I/O、数据库),应用可能会“卡顿”,尤其当通知来自 UI 线程时。应将繁重任务放到后台线程。
错误 3:监听器中抛出异常。 某个监听器中的异常可能会中断对其他监听器的通知。请将对监听器的调用包裹在 try-catch 中并记录错误。
错误 4:同一监听器被重复注册。 如果同一监听器被添加多次,它会收到重复次数的事件。请管控好注册流程,并防止重复添加。
错误 5:被观察者与观察者紧耦合。 如果被观察者了解观察者的具体实现,就破坏了低耦合。应只依赖接口(例如,TemperatureObserver、CounterListener)。
GO TO FULL VERSION