JDK 9+ 观察者模式最佳实践:告别废弃 API,拥抱 PropertyChangeSupport

JDK 9+ 观察者模式最佳实践:告别废弃 API,拥抱 PropertyChangeSupport

_

在 Java 开发中,观察者模式(Observer Pattern)是我们处理对象状态变化与事件解耦的常用利器。很多老旧的教程中仍然在使用 java.util.Observerjava.util.Observable 来实现该模式,但如果你使用的是 JDK 9 或更高版本,你会发现这两个类已经被官方打上了 @Deprecated 注解。

官方废弃它们的原因主要是其设计存在缺陷(例如 Observable 是一个类而不是接口,限制了单继承,且缺乏足够细致的事件模型)。作为替代方案,官方推荐我们使用 java.beans 包下的 PropertyChangeSupportPropertyChangeListener

今天,我们就通过一个简单的用户属性变更日志记录的例子,来演示如何使用这套现代化的 API 优雅地实现观察者模式。

1. 核心原理与角色划分

在使用 PropertyChangeSupport 的体系中,我们同样遵循观察者模式的基础逻辑:

  • 事件源(Subject):状态发生改变的对象。它负责维护监听器列表,并在状态改变时发出通知。

  • 监听器(Observer):对状态改变感兴趣的对象。它负责接收通知并执行相应的业务逻辑。

2. 代码实现

第一步:定义事件源(User 类)

我们在 User 类中引入 PropertyChangeSupport 来作为事件分发的代理。相比于继承 Observable,这种组合优于继承的方式更加灵活,不会占用宝贵的父类名额。

package com.okcl.javapattern01;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

/**
 * 用户类 (事件源/被观察者)
 */
public class User {
    // 核心:创建属性监听支持代理对象
    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
    private String userName;

    /**
     * 暴露添加监听器的方法
     *
     * @param listener 监听器实例
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    /**
     * 暴露移除监听器的方法
     *
     * @param listener 监听器实例
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    /**
     * 触发事件的核心逻辑:在修改属性时发出通知
     *
     * @param userName 新的用户名
     */
    public void setUserName(String userName) {
        String oldUserName = this.userName;
        this.userName = userName;
        // firePropertyChange 负责通知所有注册的监听器
        // 参数分别为:属性名、旧值、新值
        support.firePropertyChange("userName", oldUserName, userName);
    }
}

亮点解析firePropertyChange 方法非常聪明。它会在底层自动对比 oldValuenewValue,只有当两者的值确实发生变化时(通过 equals 方法判断),才会真正触发通知,避免了不必要的性能损耗。

第二步:定义监听器(UserLogger 类)

监听器只需要实现 PropertyChangeListener 接口,并重写 propertyChange 方法即可。

package com.okcl.javapattern01;

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

/**
 * 用户日志类 (事件监听器/观察者)
 */
public class UserLogger implements PropertyChangeListener {
    
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // evt 对象中包含了极为丰富的事件上下文信息
        System.out.println("监听到属性 [" + evt.getPropertyName() + "] 发生变化");
        System.out.println("旧值:" + evt.getOldValue());
        System.out.println("新值:" + evt.getNewValue());
        System.out.println("--------------------------------");
        
        // 此处可以自由扩展业务逻辑,例如将日志异步写入数据库或 Elasticsearch
    }
}

亮点解析:相比于老版本的 Observer 接口,PropertyChangeEvent 提供了非常清晰的上下文。你可以直接通过 getOldValue()getNewValue() 获取数据的变化轨迹,这对日志记录、数据回滚等操作极其友好。

第三步:客户端调用与测试

最后,我们将事件源与监听器组装起来。

package com.okcl.javapattern01;

public class Main {
    public static void main(String[] args) {
        User user = new User();
        UserLogger userLogger = new UserLogger();
        
        // 1. 将监听器注册到事件源上
        user.addPropertyChangeListener(userLogger);
        
        // 2. 模拟业务操作,改变对象状态
        System.out.println("第一次修改用户名:");
        user.setUserName("张三");
        
        System.out.println("第二次修改用户名:");
        user.setUserName("李四");
    }
}

3. 为什么推荐这种写法?

  1. 符合面向对象设计原则:使用组合(PropertyChangeSupport)代替继承(extends Observable),让你的实体类依然可以继承其他重要的业务父类。

  2. API 健壮性更强:内置了新旧值对比逻辑,减少了冗余的通知触发。并且事件对象 PropertyChangeEvent 携带的信息远比老版 ObserverObject arg 要丰富和具体。

  3. 零外部依赖:它是 JDK 自带的标准包(java.beans),不需要引入诸如 Guava EventBus 或 Spring Event 这样的第三方重量级依赖,非常适合轻量级开发和底层工具类的编写。

总结

随着 Java 版本的不断演进,及时跟进并替换废弃 API 是提升代码质量的重要环节。PropertyChangeSupport 为我们提供了一个标准化、低侵入且功能强大的观察者模式实现方案,强烈建议在 JDK 9+ 的项目中全面推广使用。

深入理解单例模式:DCL 双重检查锁为什么必须加 volatile? 2026-06-05
设计模式实战:用 Java 优雅实现适配器模式解决接口不兼容 2026-06-07

评论区