1.意图
定义对象间的一种依赖关系。当一个对象的状态发生改变时,所有依赖与它的对象得到通知并自动更新。
2.别名
发布-订阅(publish-subscribe)
3.动机
现在有一个数据对象,还有表格对象和柱状图对象来描述这个数据对象的信息,因此数据对象的任何改动都应该立刻通知它们。
Observer模式描述了如何建立这种关系。
4.适用性
·一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中,以使它们可以各自独立地改变和复用。
·对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
你不希望这些对象是紧密耦合的。
5.结构
6.参与者
·Subject(目标)
—目标知道它的观察者。可以有任意多个观察者观察同一个目标。
—提供注册和删除观察者对象的接口。
·Observer(观察者)
—为那些在目标发生改变时需要获得通知的对象定义一个更新接口。
·ConcreteSubject(具体目标)
—将有关状态存入各ConcreteObserver对象。
—当它的状态发生改变时,向其各个观察者发出通知。
·ConcreteObserver(具体观察者)
—维护一个指向ConcreteSubject对象的引用。
—存储有关状态,这些状态应与目标的状态保持一致。
—实现Observer的更新接口,以使自身状态与目标的状态保持一致。
7.协作
8.效果
1)抽象耦合而非紧密耦合
对于subject来说,它只知道它有很多observer,有一些简单接口,并不知道它们属于哪个具体类。
2)广播通信
subject唯一的责任就是通知它的observer,处理还是忽略取决于observer
3)更新的后果
由于一个observer并不知道其他observer的存在,它可能对改变subject的最终代价一无所知。
9.实现
1)创建从subject到observer的映射
hash,不赖
2)观察多个subject
一个observer可能观察多个subject,例如一张表格依赖于多个数据源。此时就需要拓展Update来传递关于subject的信息,可以直接将subject作为参数传递。
3)谁来调用Notify来触发更新
两个选择:
改变subject后自动调用。用户得了MVP,自动程序是躺赢狗。
用户来调用,这样就可以在一系列状态改变后一次性更新(commit),但是用户可能有点健忘。
4)悬挂引用(“空指针问题”)
删除一个目标时应注意不要在其观察者中遗留对该目标的悬挂引用。一种避免悬挂引用的方法是,当一个目标被删除时,让它通知它的观察者将对该目标的引用复位。不能简单地删除观察者,因为可能它们还在观察其他的目标。
5)保证state一致
嵌套调用:子类重写父类方法时意外触发父类逻辑
public abstract class Subject {
// 父类定义的状态变更方法
public void updateState() {
// 基础状态更新逻辑
doUpdate();
notifyObservers(); // 触发通知
}
protected abstract void doUpdate(); // 子类扩展点
}
public class ConcreteSubject extends Subject {
private int value;
@Override
protected void doUpdate() {
value++; // 子类扩展的状态修改
// 假设此处误调用了父类的updateState(),导致重复通知
super.updateState(); // 错误:触发父类通知逻辑
}
}
-
父类
updateState()
调用doUpdate()
。 -
子类
doUpdate()
调用super.updateState()
。 -
父类
updateState()
再次触发notifyObservers()
。
最终导致notifyObservers()
被递归调用多次。
首次 notifyObservers()
时,value
可能仅部分更新,观察者获取到中间状态。完辣!
你可以用抽象的Subject类中的模板方法(Template Method)发送通知来避免这种错误。定义那些子类可以重定义的原语操作,并将Notify作为模板方法中的最后一个操作,这样当子类重定义了Subject的操作时,还可以保证该对象的状态是自身一致的。
public abstract class Subject {
// 模板方法:定义算法骨架(不可重写)
public final void performUpdate() {
beforeUpdate(); // 前置钩子(可选)
doUpdate(); // 子类实现具体状态变更
afterUpdate(); // 后置钩子(可选)
notifyObservers(); // 确保在状态一致后触发通知
}
// 原语操作:子类必须实现的纯状态变更逻辑
protected abstract void doUpdate();
// 可选的扩展点(空实现或默认逻辑)
protected void beforeUpdate() {}
protected void afterUpdate() {}
}
public class ConcreteSubject extends Subject {
private int value;
private String status;
@Override
protected void doUpdate() {
value++;
status = (value % 2 == 0) ? "EVEN" : "ODD"; // 原子性更新
}
}
Subject subject = new ConcreteSubject();
subject.performUpdate(); // 状态变更和通知由模板方法严格管控
6)推/拉模型
subject通常需要广播一些其他的信息,这些信息作为Update的一个参数传递出去。
推模型:发送关于改变的详细信息,不管observer是否需要。
拉模型:发送最小的通知,由observer询问细节。
拉模型强调的是目标不知道它的observer,而推模型假定目标知道一些observer需要的信息。
推模型可能使得observer相对难以复用,因为subject对observer的假定可能并不总是正确的。拉模型可能效率较差。
7)指定具体的改变类型
你可以拓展Add接口,让各个observer注册为仅仅对特定事件“感兴趣”,这么做提高了更新的效率。一种实现方式是使用Aspect:
-
事件类型(Aspect):观察者可注册为对特定事件类型(如
"DATA_UPDATE"
、"STATUS_CHANGE"
)感兴趣。 -
高效通知:事件触发时,仅通知订阅该事件类型的观察者。
-
解耦参数传递:将事件类型和数据封装为事件对象,传递上下文信息。
// 事件对象:封装事件类型和相关数据
public class Event {
private final String type;
private final Object data;
}
// 观察者接口:支持按事件类型处理
public interface Observer {
void update(Event event);
}
public class Subject {
// 注册表:事件类型 → 观察者列表(线程安全)
private final Map<String, List<Observer>> eventObservers =
new ConcurrentHashMap<>();
// 注册观察者到特定事件类型
public void Add(String eventType, Observer observer) {
eventObservers.computeIfAbsent(
eventType,
k -> new CopyOnWriteArrayList<>()
)
.add(observer);
}
}
// 日志观察者:仅处理 "LOG_EVENT" 事件
public class LogObserver implements Observer {
@Override
public void update(Event event) {
if ("LOG_EVENT".equals(event.getType())) {
System.out.println("日志记录: " + event.getData());
}
}
}
// 数据更新观察者:处理 "DATA_UPDATE" 事件
public class DataUpdateObserver implements Observer {
@Override
public void update(Event event) {
if ("DATA_UPDATE".equals(event.getType())) {
System.out.println("数据已更新: " + event.getData());
}
}
}
8)关系复杂时
当目标和观察者间的依赖关系特别复杂时,可能需要一个维护这些关系的对象。我们称这样的对象为更改管理器 (ChangeManager)。它的目的是尽量减少观察者反映其目标的状态变化所需的工作量。例如,如果一个操作涉及对几个相互依赖的目标进行改动,就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。
ChangeManager有三个责任:
·它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之亦然。
·它定义一个特定的更新策略。
·根据一个目标的请求,它更新所有依赖于这个目标的观察者。
ChangeManager是Mediator模式的实例。通常只有一个ChangeManager并且全局可见。Singleton:天哪,这简直就是我!
实现放在Mediator里Mediator(中介者) --对象行为型模式-CSDN博客
10.代码示例
public abstract class Subject {
// 用弱引用避免 Observer 永久被挂住
private final Map<Observer, Set<EventType>> observers =
new WeakHashMap<>();
/** 注册:可指定多个感兴趣的事件类型,不传则表示对所有事件都感兴趣 */
public void addObserver(Observer o, EventType... eventType) {
Set<EventType> set =
observers.computeIfAbsent(
o, k -> EnumSet.noneOf(EventType.class)
);
if (eventType.length == 0) { // 0 means all
set.addAll(Arrays.asList(EventType.values()));
} else {
set.addAll(Arrays.asList(eventType));
}
}
/** 退订:彻底取消所有事件 */
public void removeObserver(Observer o) {
observers.remove(o);
}
/** 模板方法:子类修改状态后都要调用它(或自动调用) */
protected void notifyObservers(Event event) {
// 1. 先快照,避免在迭代时 Observer 被移除
List<Map.Entry<Observer,Set<EventType>>> snapshot =
new ArrayList<>(observers.entrySet());
for(var entry : snapshot) {
Observer o = entry.getKey();
Set<EventType> eventTypes = entry.getValue();
// 2. 只通知感兴趣的 Observer
if(eventTypes.contains(event.getEventType())) {
// 3. 触发更新
o.update(this, event);
}
}
}
// —— 以下是两种更新触发策略 ——
/** 自动通知:每次修改后立即通知 */
protected final void changeStateAuto(Runnable stateChanger, Object payload) {
stateChanger.run();
notifyObservers(new Event(EventType.STATE_CHANGE, payload));
}
/** 手动批量通知:调用者可以修改多次,最后一次性通知 */
protected final void changeStateManual(Runnable stateChanger) {
stateChanger.run();
// 不自动通知,需外部显式调用 notifyObservers(...)
}
}
public class ConcreteSubject extends Subject {
private int state;
public int getState() {
return state;
}
/** 例:自动通知 */
public void setStateAuto(int newState) {
changeStateAuto(() -> this.state = newState, newState);
}
/** 例:手动通知 */
public void setStateManual(int newState) {
changeStateManual(() -> this.state = newState);
}
public void commit() {
// 批量修改完毕后手动一次性通知
notifyObservers(new Event(EventType.DATA_UPDATE, state));
}
}
package Observer;
public interface Observer {
/**
* @param subject 触发更新的 Subject
* @param event 事件类型 + 附加数据
*/
void update(Subject subject, Event event);
}
package Observer;
import java.util.ArrayList;
import java.util.List;
public class ConcreteObserver implements Observer {
private final List<Subject> subjects = new ArrayList<>();
public ConcreteObserver(Subject... subs) {
for (Subject s : subs) {
// 注册时默认感兴趣的事件类型
s.addObserver(this, EventType.STATE_CHANGE, EventType.DATA_UPDATE);
subjects.add(s);
}
}
@Override
public void update(Subject subject, Event event) {
// 拉模型:根据需要 pull 细节
// instanceof用于检查一个对象是否是某个类(或其子类)的实例,或者是否实现了某个接口。
if (subject instanceof ConcreteSubject) {
int newState = ((ConcreteSubject) subject).getState();
System.out.printf("Observer %s 收到 %s 的 %s 事件,state=%d%n",
this, subject, event.getEventType(), newState);
}
// 或者用推模型:Object payload = event.getPayload();
}
/** 在不再需要时,清理所有订阅,避免悬挂引用 */
public void detachAll() {
for (Subject s : subjects) {
s.removeObserver(this);
}
subjects.clear();
}
}
package Observer;
/**
* 一个综合示例,演示:
* - 多 Subject 场景
* - 多事件类型过滤
* - 推/拉模型
* - 自动 vs 手动通知
* - 取消订阅
* - 自定义事件
*/
public class Client {
public static void main(String[] args) {
// --- 1. 创建多个 Subject ---
ConcreteSubject subject1 = new ConcreteSubject();
ConcreteSubject subject2 = new ConcreteSubject();
// --- 2. 创建不同类型的 Observer ---
// 2.1 只订阅 STATE_CHANGE 事件的 Observer
Observer stateOnlyObserver = new Observer() {
@Override
public void update(Subject subject, Event event) {
System.out.printf("[StateOnly] 收到 %s 的 %s,payload=%s, 直接拉取 state=%d\n",
subject, event.getEventType(), event.getPayload(),
(subject instanceof ConcreteSubject ? ((ConcreteSubject)subject).getState() : -1)
);
}
};
subject1.addObserver(stateOnlyObserver, EventType.STATE_CHANGE);
// 2.2 只订阅 DATA_UPDATE 和 CUSTOM 事件的 Observer
Observer dataOrCustomObserver = new Observer() {
@Override
public void update(Subject subject, Event event) {
System.out.printf("[DataOrCustom] 收到 %s 的 %s,payload=%s\n",
subject, event.getEventType(), event.getPayload());
}
};
subject1.addObserver(dataOrCustomObserver, EventType.DATA_UPDATE, EventType.CUSTOM);
subject2.addObserver(dataOrCustomObserver, EventType.DATA_UPDATE, EventType.CUSTOM);
// 2.3 使用已有的 ConcreteObserver,默认订阅 STATE_CHANGE + DATA_UPDATE
ConcreteObserver multiObserver = new ConcreteObserver(subject1, subject2);
System.out.println("--- 3. 自动通知 (Auto Notify) ---");
subject1.setStateAuto(10);
System.out.println("--- 4. 手动批量通知 (Manual Notify) ---");
subject2.setStateManual(20);
subject2.setStateManual(30);
System.out.println("(还未通知,接下来手动 commit)");
subject2.commit();
System.out.println("--- 5. 自定义事件 (Custom Event) ---");
// 直接用模板方法发送 CUSTOM 类型事件,并推送自定义 payload
subject1.notifyObservers(new Event(EventType.CUSTOM, "CustomPayload123"));
System.out.println("--- 6. 取消订阅 (Unsubscribe) ---");
subject1.removeObserver(stateOnlyObserver);
System.out.println("stateOnlyObserver 已退订 state_change,再次 setStateAuto(100) 只剩下其他两位了");
subject1.setStateAuto(100);
// 最后清理:detach 所有 multiObserver 对所有 Subjects 的订阅
multiObserver.detachAll();
subject1.removeObserver(dataOrCustomObserver);
subject2.removeObserver(dataOrCustomObserver);
}
}
11.相关模式
Mediator,Singleton