观察者模式

45 阅读3分钟

使用 PropertyChangeListener 和 PropertyChangeSupport

1. 创建 "被观察者" (Subject / Observable)

我们创建一个 NewsAgency 类。它不再继承 Observable,而是内部持有一个 PropertyChangeSupport 对象来管理所有的监听器。

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

// "观察者" - 新闻频道
public class NewsChannel implements PropertyChangeListener {

    private String news;
    private String channelName;

    public NewsChannel(String channelName) {
        this.channelName = channelName;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // 当接收到通知时,更新自己的消息
        this.news = (String) evt.getNewValue();
        System.out.println(channelName + " 收到最新消息: " + this.news);
        System.out.println("    (事件源: " + evt.getPropertyName() + ", 旧消息: " + evt.getOldValue() + ")");
    }
}

代码解析:

  • NewsAgency 类中包含一个 PropertyChangeSupport 实例。
  • add/removePropertyChangeListener 方法直接委托给 support 对象来处理监听器的注册和注销。
  • setNews 方法被调用时,它会使用 support.firePropertyChange 来通知所有已注册的监听器。这个方法可以传递属性名、旧值和新值,提供了比旧 Observer 模式更丰富的上下文信息。 [Observer is deprecated in Java 9. What should we use instead of it ...]

2. 创建 "观察者" (Observer)

我们创建一个 NewsChannel 类,它实现了 PropertyChangeListener 接口。

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

// "观察者" - 新闻频道
public class NewsChannel implements PropertyChangeListener {

    private String news;
    private String channelName;

    public NewsChannel(String channelName) {
        this.channelName = channelName;
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // 当接收到通知时,更新自己的消息
        this.news = (String) evt.getNewValue();
        System.out.println(channelName + " 收到最新消息: " + this.news);
        System.out.println("    (事件源: " + evt.getPropertyName() + ", 旧消息: " + evt.getOldValue() + ")");
    }
}

代码解析:

  • NewsChannel 实现了 PropertyChangeListener 接口。
  • 核心逻辑在 propertyChange 方法中。当 NewsAgency 触发事件时,这个方法会被自动调用。
  • PropertyChangeEvent 对象 evt 包含了所有事件相关的信息,如事件源、属性名、旧值和新值,非常方便。

3. 运行示例

最后,我们编写一个 main 方法来将它们连接起来并测试。

public class Main {
    public static void main(String[] args) {
        // 创建被观察者(新闻机构)
        NewsAgency agency = new NewsAgency();

        // 创建观察者(新闻频道)
        NewsChannel channel1 = new NewsChannel("第一频道");
        NewsChannel channel2 = new NewsChannel("第二频道");

        // 将观察者注册到被观察者
        agency.addPropertyChangeListener(channel1);
        agency.addPropertyChangeListener(channel2);

        // 发布第一条新闻
        System.out.println("--- 发布第一条新闻 ---");
        agency.setNews("Java 25 正式发布!");

        System.out.println("\n--- 第二频道取消订阅 ---");
        agency.removePropertyChangeListener(channel2);
        
        // 发布第二条新闻
        System.out.println("\n--- 发布第二条新闻 ---");
        agency.setNews("观察者模式有了新的实现方式。");
    }
}

预期输出:

复制代码

--- 发布第一条新闻 --- 第一频道 收到最新消息: Java 25 正式发布! (事件源: news, 旧消息: null) 第二频道 收到最新消息: Java 25 正式发布! (事件源: news, 旧消息: null)

--- 第二频道取消订阅 ---

--- 发布第二条新闻 --- 第一频道 收到最新消息: 观察者模式有了新的实现方式。 (事件源: news, 旧消息: Java 25 正式发布!)

总结

与旧的 Observable/Observer 相比,使用 PropertyChangeListenerPropertyChangeSupport 的优势非常明显:

  1. 基于组合,更灵活:你的业务类无需为了实现观察者模式而继承一个特定的类,可以自由继承其他任何类。
  2. 更丰富的事件模型PropertyChangeEvent 对象提供了更详细的上下文信息,包括属性名称、旧值和新值,而不仅仅是一个简单的“已更新”信号。 [Replacing Observer-Observable (inheritance vs. composition) · GitHub]
  3. 遵循现代 Java 设计实践:这种方式更符合现代面向对象设计中“组合优于继承”的原则。

除了 java.beans 包,在更复杂的应用或响应式编程场景中,开发者也常常会使用功能更强大的第三方库,如 RxJava 或在 Kotlin 中的 Flow,它们提供了对观察者模式更高级、更强大的实现。