概述
我们可以发现这样一个场景:如果你订阅了一份杂志或报纸, 那就不需要再去报摊查询新出版的刊物了。
- 出版社 (即应用中的 “发布者(publisher)”) 会在刊物出版后 (甚至提前) 直接将最新一期寄送至你的邮箱中。
出版社负责维护订阅者(subscribers)列表, 了解订阅者对哪些刊物感兴趣。
- 当订阅者希望出版社停止寄送新一期的杂志时, 他们可随时从该列表中退出。
观察者模式(Observer)是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。
- 拥有一些值得关注的状态的对象通常被称为
目标
, - 由于它要将自身的状态改变通知给其他对象, 我们也将其称为
发布者 (publisher)
。 - 所有希望关注发布者状态变化的其他对象被称为
订阅者 (subscribers)
。
结构如下
其具体的结构如下
场景选择
当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。
- 当你使用图形用户界面类时通常会遇到一个问题。
比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。
- 观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。
可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。
当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用
。
- 订阅列表是动态的, 因此订阅者可随时加入或离开该列表。
目前代码中的实际应用
应用示例: 观察者模式在 Java 代码中很常见, 特别是在 GUI 组件中。 它提供了在不与其他对象所属类耦合的情况下对其事件做出反应的方式。
下面是核心 Java 程序库中该模式的一些示例:
- java.util.Observer/java.util.Observable (极少在真实世界中使用)
- java.util.EventListener的所有实现 (几乎广泛存在于 Swing 组件中)
- javax.servlet.http.HttpSessionBindingListener
- javax.servlet.http.HttpSessionAttributeListener
- javax.faces.event.PhaseListener
识别方法加粗样式: 该模式可以通过将对象存储在列表中的订阅方法, 和对于面向该列表中对象的更新方法的调用来识别
。
伪代码实现
发布者 (Publisher)
发布者 (Publisher) 会向其他对象发送值得关注的事件。
- 事件会在
发布者自身状态改变或执行特定行为后发生
。 - 发布者中
包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架
。 - 当
新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的
。
示例代码
// 保存所有订阅此主题的观察者,观察者的 数量是任意的。
// 定义 添加观察者 (Attach) 和 删除观察者 (Detach) 的接口。
abstract class Publisher {protected String name;protected String state;protected List<Subscriber> subscribers = new ArrayList<Subscriber>();public abstract String getState();public abstract void setState(String state);// 发布public abstract void Notify();public Publisher(String name) {this.name = name;}// 添加观察者public void Attach(Subscriber subscriber) {observers.add(observer);}// 删除观察者public void Detach(Subscriber subscriber) {observers.remove(observer);}
}
订阅者 (Subscriber)
订阅者 (Subscriber) 接口声明了通知接口
。
- 在绝大多数情况下, 该接口仅包含一个 update更新方法。
- 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。
示例代码
// 观察者类,定义更新接口 (Update),当收到 Subject 的通知时,Observer 需要同步更新信息
abstract class Subscriber {protected String name;// 监听的发布者protected Publisher publisher;public Subscriber(String name, Publisher publisher) {this.name = name;this.publisher= publisher;}public abstract void Update();
}
上下文信息
订阅者通常需要一些上下文信息来正确地处理更新。
- 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。
- 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。
如上示例所示,上下文信息便是status和name
具体发布者 (Concrete Publisher)
具体发布者 (Concrete Publisher)相当于订阅者需要监听的类型(因为这个类型有很多种,大部分情况下不可能只有一种发布者)
示例代码
class ConcretePublisher extends Publisher{public ConcretePublisher(String name) {super(name);}@Overridepublic String getState() {return state;}@Overridepublic void setState(String state) {this.state = state;}@Overridepublic void Notify() {System.out.println("======= " + this.name + "主题发布新消息 =======");for (Subscriber subscriber : subscribers) {subscriber.Update();}}
}
具体订阅者类(Concrete Subscriber )
具体订阅者 (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。
- 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。
ConcreteSubscriber : 具体订阅者类,实现 Subscriber 的更新接口 (Update),以便和 Publisher 同步状态信息
示例代码
class ConcreteSubscriber extends Subscriber {private String state;public ConcretePublisher(String name, Publisher publisher) {super(name, publisher);}@Overridepublic void Update() {state = subject.getState();System.out.println(this.name + "收到当前状态:" + state);}
}
客户端 (Client)
客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新
示例代码
public class ObserverPattern {public static void main(String[] args) {ConcretePublisher concretePublisher = new ConcretePublisher("天气");ConcreteSubscriber sub1 = new ConcreteSubscriber("张三", concretePublisher);ConcreteSubscriber sub2= new ConcreteSubscriber("李四", concretePublisher);ConcreteSubscriber sub3= new ConcreteSubscriber("王五", concretePublisher);concretePublisher.Attach(sub1);concretePublisher.Attach(sub2);concretePublisher.Attach(sub3);concretePublisher.setState("今天下雨");concretePublisher.Notify();concretePublisher.Detach(sub2);concretePublisher.setState("明天天晴");concretePublisher.Notify();}
}
/*
======= 天气主题发布新消息 =======
张三收到当前状态:今天下雨
李四收到当前状态:今天下雨
王五收到当前状态:今天下雨
======= 天气主题发布新消息 =======
张三收到当前状态:明天天晴
王五收到当前状态:明天天晴*/
实现方式
下面总结一下实现的步骤,大致可分为下面的几步
-
仔细检查业务逻辑, 试着将其拆分为两个部分:
- 独立于其他代码的核心功能将作为发布者;
- 其他代码则将转化为一组订阅类。
-
声明订阅者接口。 该接口至少应声明一个 update方法。
-
声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。
记住发布者仅通过订阅者接口与它们进行交互
。
-
确定存放实际订阅列表的位置并实现订阅方法。
- 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。
- 但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。
-
创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。
-
在具体订阅者类中实现通知更新的方法。
- 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。
- 但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。
-
客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。