CodeGym /课程 /JAVA 25 SELF /Observer 模式(观察者)

Observer 模式(观察者)

JAVA 25 SELF
第 50 级 , 课程 3
可用

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.Observablejava.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:被观察者与观察者紧耦合。 如果被观察者了解观察者的具体实现,就破坏了低耦合。应只依赖接口(例如,TemperatureObserverCounterListener)。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION