观察者模式是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个“观察” 该对象的其他对象。
亦称: 事件订阅者、监听者、Event-Subscriber、Listener、Observer
核心 Java 程序库中该模式的一些示例:
- java.util.Observer/ java.util.Observable (极少在真实世界中使用)
- java.util.EventListener的所有实现 (几乎广泛存在于 Swing 组件中)
- javax.servlet.http.HttpSessionBindingListener
- javax.servlet.http.HttpSessionAttributeListener
- javax.faces.event.PhaseListener
识别方法: 该模式可以通过将对象存储在列表中的订阅方法, 和对于面向该列表中对象的更新方法的调用来识别。
观察者模式结构
样例
模拟事件订阅
观察者模式在文本编辑器的对象之间建立了间接的合作关系。 每当 编辑器
(Editor) 对象改变时, 它都会通知其订阅者。 邮件通知监听器
(EmailNotificationListener) 和 日志开启监听器
(LogOpenListener) 都将通过执行其基本行为来对这些通知做出反应。
订阅者类不与编辑器类相耦合, 且能在需要时在其他应用中复用。 编辑器
类仅依赖于抽象订阅者接口。 这样就能允许在不改变编辑器代码的情况下添加新的订阅者类型。
基础发布者
package behavioral.ovserver.publisher;
import behavioral.ovserver.listeners.EventListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManager {
Map<String, List<EventListener>> listMap = new HashMap<>();
public EventManager(String... operations) {
for (String operation : operations) {
this.listMap.put(operation, new ArrayList<>());
}
}
public void subscribe(String eventType, EventListener listener) {
List<EventListener> users = listMap.get(eventType);
users.add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> users = listMap.get(eventType);
users.remove(listener);
}
public void notify(String eventType, File file) {
List<EventListener> users = listMap.get(eventType);
for (EventListener listener : users) {
listener.update(eventType, file);
}
}
}
具体发布者,由其他对象追踪
package behavioral.ovserver;
import behavioral.ovserver.publisher.EventManager;
import java.io.File;
/**
* 具体发布者
*/
public class Editor {
public EventManager events;
private File file;
public Editor() {
this.events = new EventManager("open", "save");
}
public void openFile(String filePath) {
this.file = new File(filePath);
events.notify("open", file);
}
public void saveFile() throws Exception {
if (this.file != null) {
events.notify("save", file);
} else {
throw new Exception("Please open a file first.");
}
}
}
通用的观察者接口
package behavioral.ovserver.listeners;
import java.io.File;
public interface EventListener {
void update(String eventType, File file);
}
收到通知后发送邮件
package behavioral.ovserver.listeners;
import java.io.File;
public class EmailNotificationListener implements EventListener{
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, File file){
System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
收到通知后在日志中记录一条消息
package behavioral.ovserver.listeners;
import java.io.File;
public class LogOpenListener implements EventListener {
private File log;
public LogOpenListener(String filePath) {
this.log = new File(filePath);
}
@Override
public void update(String eventType, File file) {
System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
}
}
测试
package behavioral.ovserver;
import behavioral.ovserver.listeners.EmailNotificationListener;
import behavioral.ovserver.listeners.LogOpenListener;
public class Demo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("test.txt"));
editor.events.subscribe("save", new EmailNotificationListener("hrm@qq.com"));
try {
editor.openFile("test.txt");
editor.saveFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*Save to log test.txt: Someone has performed open operation with the following file: test.txt
* Email to hrm@qq.com: Someone has performed save operation with the following file: test.txt
*/
适用场景
-
当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时,可使用观察者模式。
当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。
观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。
-
当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。
订阅列表是动态的, 因此订阅者可随时加入或离开该列表。
实现方式
-
仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类。
-
声明订阅者接口。 该接口至少应声明一个
update
方法。 -
声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互。
-
确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。
但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。
-
创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。
-
在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。
但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下,发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。
-
客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。
观察者模式优点
- 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
- 你可以在运行时建立对象之间的联系。
观察者模式缺点
- 订阅者的通知顺序是随机的。