在 Java 开发中,观察者模式(Observer Pattern)是我们处理对象状态变化与事件解耦的常用利器。很多老旧的教程中仍然在使用 java.util.Observer 和 java.util.Observable 来实现该模式,但如果你使用的是 JDK 9 或更高版本,你会发现这两个类已经被官方打上了 @Deprecated 注解。
官方废弃它们的原因主要是其设计存在缺陷(例如 Observable 是一个类而不是接口,限制了单继承,且缺乏足够细致的事件模型)。作为替代方案,官方推荐我们使用 java.beans 包下的 PropertyChangeSupport 和 PropertyChangeListener。
今天,我们就通过一个简单的用户属性变更日志记录的例子,来演示如何使用这套现代化的 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 方法非常聪明。它会在底层自动对比 oldValue 和 newValue,只有当两者的值确实发生变化时(通过 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. 为什么推荐这种写法?
符合面向对象设计原则:使用组合(
PropertyChangeSupport)代替继承(extends Observable),让你的实体类依然可以继承其他重要的业务父类。API 健壮性更强:内置了新旧值对比逻辑,减少了冗余的通知触发。并且事件对象
PropertyChangeEvent携带的信息远比老版Observer的Object arg要丰富和具体。零外部依赖:它是 JDK 自带的标准包(
java.beans),不需要引入诸如 Guava EventBus 或 Spring Event 这样的第三方重量级依赖,非常适合轻量级开发和底层工具类的编写。
总结
随着 Java 版本的不断演进,及时跟进并替换废弃 API 是提升代码质量的重要环节。PropertyChangeSupport 为我们提供了一个标准化、低侵入且功能强大的观察者模式实现方案,强烈建议在 JDK 9+ 的项目中全面推广使用。