简介
观察者是一种行为设计模式, 允许你定义一种订阅通知机制, 可在事件发生时通知多个“观察/订阅”该对象的其他对象。
问题
假如你有两种类型的对象: 顾客和商店。顾客对某个新品非常感兴趣,他可以每天来商店看产品是否到货。但如果商品尚未到货时,顾客就会空手而归。
或者,每次产品到货时,商店可以向所有顾客发送邮件。这样,部分顾客就无需反复前往商店了,但也可能会惹恼对新品没有兴趣的其他顾客。
我们似乎遇到了一个矛盾:要么让顾客浪费时间检查产品是否到货,要么让商店去通知所有顾客。
解决
拥有被关注状态的对象,比如商店,通常被称为目标,由于它要在自身状态改变时通知给其他对象,我们也将其称为发布者(publisher)。 所有关注发布者状态变化的其他对象被称为订阅者(subscribers),比如顾客。
观察者模式建议你为发布者类添加订阅机制,让每个对象都能订阅或取消订阅发布者的事件。这个机制包括 :
- 一个成员变量,它是一个列表,用于存储所有订阅者对象的引用。
- 几个用于添加或删除列表中订阅者的公有方法。
如果发生了发布者事件,它就要遍历订阅者并调用其对象的特定通知方法。
实际应用中可能会有十几个不同的订阅者类订阅同一个发布者类的事件,你不希望发布者与所有这些类相耦合。此外如果别人也会订阅这个发布者类, 你甚至不知道这些订阅者类是什么。
因此,所有订阅者都必须实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。
如果你的应用中有多个不同类型的发布者,且希望订阅者可兼容所有发布者,那么你甚至可以进一步让所有订阅者遵循同样的接口。该接口仅需定义几个订阅方法即可。这样订阅者就能在不与具体发布者类 耦合的情况下通过接口订阅发布者的状态变化通知。
代码
// Publisher 接口
interface StorePublisher {
void subscribe(CustomerSubscriber subscriber);
void unsubscribe(CustomerSubscriber subscriber);
void notifySubscribers(String product);
}
// 具体 Publisher 类
class IPhoneStore implements StorePublisher {
private List<CustomerSubscriber> customers = new ArrayList<>();
@Override
public void subscribe(CustomerSubscriber subscriber) {
customers.add(subscriber);
}
@Override
public void unsubscribe(CustomerSubscriber subscriber) {
customers.remove(subscriber);
}
@Override
public void notifySubscribers(String product) { // 触发所有订阅者更新
for (CustomerSubscriber customer : customers) {
customer.update(product);
}
}
public void restockProduct(String product) {
System.out.println("新品到货:" + product);
notifySubscribers(product);
}
}
// Subscriber 接口
interface CustomerSubscriber {
void update(String product);
}
// 具体 Subscriber 类
class PremiumCustomer implements CustomerSubscriber {
private String name;
public PremiumCustomer(String name) {
this.name = name;
}
@Override
public void update(String product) { // 实现标准通知接口
System.out.println("[尊享客户]" + name + ",您关注的" + product + "已到店");
}
}
// Client 客户端配置
public class ObserverDemo {
public static void main(String[] args) {
IPhoneStore store = new IPhoneStore();
CustomerSubscriber alice = new PremiumCustomer("Alice");
CustomerSubscriber bob = new PremiumCustomer("Bob");
// 动态添加订阅
store.subscribe(alice);
store.subscribe(bob);
// 模拟到货事件
store.restockProduct("iPhone15");
// 动态移除订阅
store.unsubscribe(bob);
store.restockProduct("iPhone15 Pro");
}
}
关键实现说明
- IPhoneStore 作为具体发布者,实现订阅管理和产品到货通知
- 通过接口解耦使得新订阅者类型可以扩展而不影响发布者
- 客户端通过subscribe()/unsubscribe()动态维护订阅列表
总结
- 发布者(Publisher)会向其他对象发送事件。事件会在发布者自身状态改变或执行特定行为后发生。发布者中包含一个加入新订阅者和当前删除旧订阅者的方法。
- 当新事件发生时,发送者会遍历订阅列表并调用每个订阅者对象 的通知方法。 这个方法是在订阅者接口中声明的。
- 订阅者(Subscriber)接口声明了通知接口。在绝大多数情况下 这个接口只包含一个 update 更新 方法。 该方法可以拥有多个参 数, 让发布者能在事件发生时传递事件的详细信息。
- 具体订阅者(Concrete Subscribers) 可以执行一些操作来回应发布者的通知。所有具体订阅者类都实现了同样的接口,所以发布者不需要与具体类相耦合。
- 订阅者通常需要一些上下文信息来正确地处理更新。所以发布者通常会把一些上下文数据作为通知方法的参数。 发布者也可以把自己作为参数进行传递,使订阅者直接获取所需的数据
- 客户端(Client)会分别创建发布者和订阅者对象,然后向发布者注册订阅者。