观察者模式让发布者维护订阅关系,在状态变化时通知相关对象。订阅公众号是便于理解的类比,但下面讲的是进程内对象回调,不是微信内部实现,也不是具有持久化和重试保证的消息系统。
这些概念来自我阅读《Head First 设计模式》时的学习整理。
注册、取消与通知
发布者通常提供注册、取消和通知三个入口;观察者实现更新方法。发布者不必知道每个观察者内部如何更新,只按约定发出通知。
Java 早期提供 Observable 类和 Observer 接口。Observable 是可继承的普通类,不是抽象类。以下保留这一历史 API 的最小示例:
import java.util.Observable;
import java.util.Observer;
public class ObserverDemo {
static class Person extends Observable {
private String name;
public String getName() {
return name;
}
public void setName(String value) {
name = value;
setChanged();
notifyObservers(value);
}
}
public static void main(String[] args) {
Person person = new Person();
Observer pushed = (subject, value) ->
System.out.println("收到名字:" + value);
Observer pulled = (subject, value) ->
System.out.println("回读名字:" +
((Person) subject).getName());
person.addObserver(pushed);
person.addObserver(pulled);
person.setName("Alice");
person.deleteObserver(pulled);
person.setName("Bob");
}
}
这是旧 API 的教学程序,没有外部业务数据。第一轮两个观察者都收到通知;取消 pulled 后,后续通知不再发给它。程序不依赖回调的先后顺序。
为什么先 setChanged,再 notifyObservers
Java 8 Observable 文档 说明:只有对象被标记为发生变化,notifyObservers 才通知观察者;通知之后变化标志会被清除。
把“状态更新”和“决定何时通知”分开,允许一次通知代表一组更新,而不是每个字段修改都立即广播。是否合并、何时发送,应由业务语义决定,不要漏掉标记后误以为观察者注册失败。
推数据和拉状态,有不同的时间语义
示例中 notifyObservers(value) 把名字作为参数推送;另一个观察者通过发布者对象调用 getName() 回读。
推送让通知携带明确的数据;拉取则允许接收者只读自己需要的内容。但如果状态在回读前又改变,取到的可能不是通知发生时的值。需要快照语义时,应定义不可变事件或版本,而不只传一个随时变化的对象引用。
不把回调机制当成可靠投递
API 不承诺通知顺序,这不等于实现使用随机算法。若业务依赖先后顺序、异常隔离或跨线程可见性,需要显式设计,不能靠观察一次打印顺序推断。
Observable/Observer 自 Java 9 起已弃用,其模型和事件支持有限;可参考 Java 9 API。新代码应按需求选择明确的事件接口或其他机制。进程内回调不会自动提供持久化、跨进程通信、重试或去重,这些需要额外实现。