前言:
观察者模式 亦称: 事件订阅者、监听者、Event-Subscriber、Listener、Observer,是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。根据应用场景的不同,观察者模式会对应不同的代码实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。目的是将观察者和被观察者代码解耦。
GoF 的《设计模式》定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
目录
一、结构
-
发布者 (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。
-
当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。
-
订阅者 (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个
update
更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。 -
具体订阅者 (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。
-
订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。
-
客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。
二、代码实现
学生订阅期刊:
/**
* @Description: 观察者接口 订阅期刊
*
* @Author HJW
* @Date 2021/4/23
* @Version V1.0
**/
public interface Subscriber {
void update(String type, String body);
}
/**
* @Description: 学生观察者
*
* @Author HJW
* @Date 2021/4/23
* @Version V1.0
**/
public class Student implements Subscriber{
/**
* 学号
*/
private String number;
/**
* 名称
*/
private String name;
public Student(String number, String name) {
this.number = number;
this.name = name;
}
@Override
public void update(String type, String body) {
System.out.println(name + "期刊类型:" + type + " 期刊内容:" + body);
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"number='" + number + '\'' +
", name='" + name + '\'' +
'}';
}
}
/**
* @Description: 期刊发布者
*
* @Author HJW
* @Date 2021/4/23
* @Version V1.0
**/
public interface Publisher {
/**
* 订阅
*
* @param type 期刊类型
* @param student 学生
*/
void subscribe(String type, Subscriber student);
/**
* 取消订阅
*
* @param type 期刊类型
* @param student 学生
*/
void unsubscribe(String type, Subscriber student);
/**
* 发布信息
*
* @param type 期刊类型
* @param body 期刊内容
*/
void publish(String type, String body);
}
/**
* @Description: 报刊实际发布者
*
* @Author HJW
* @Date 2021/4/23
* @Version V1.0
**/
public class PeriodicalPublisher implements Publisher{
/**
* 订阅各类期刊的学生
*/
private static Map<String, List<Subscriber>> subscriberMap = new HashMap<>(16);
/**
* 期刊类型
*/
private static Set<String> periodicalTypeSet = new HashSet<>();
static {
periodicalTypeSet.add("科技类");
periodicalTypeSet.add("历史类");
periodicalTypeSet.add("计算机类");
periodicalTypeSet.add("哲学类");
periodicalTypeSet.add("娱乐类");
periodicalTypeSet.forEach(periodicalType -> {
List<Subscriber> studentList = new ArrayList<>();
subscriberMap.put(periodicalType, studentList);
});
}
/**
* 订阅
*
* @param type 期刊类型
* @param student 学生
*/
@Override
public void subscribe(String type, Subscriber student) {
if (!periodicalTypeSet.contains(type)) {
System.out.println("暂时无" + type + "期刊,敬请期待!!!");
return;
}
subscriberMap.get(type).add(student);
System.out.println(student + "订阅" + type + "期刊");
}
/**
* 取消订阅
*
* @param type 期刊类型
* @param student 学生
*/
@Override
public void unsubscribe(String type, Subscriber student) {
if(subscriberMap.get(type).contains(student)) {
subscriberMap.get(type).remove(student);
System.out.println(student + "取消订阅" + type + "类期刊");
}
}
/**
* 发布信息
*
* @param type 期刊类型
* @param body 期刊内容
*/
@Override
public void publish(String type, String body) {
subscriberMap.get(type).forEach(student -> {
student.update(type, body);
});
}
/**
* 获取期刊类型
*
* @return periodicalTypeSet
*/
public static Set<String> getPeriodicalTypeSet() {
return periodicalTypeSet;
}
/**
* 获取期刊类型
*
* @return periodicalTypeSet
*/
public static void addPeriodicalTypeSet(String type) {
periodicalTypeSet.add(type);
List<Subscriber> studentList = new ArrayList<>();
subscriberMap.put(type, studentList);
}
}
/**
* @Description: 客户端测试类
*
* @Author HJW
* @Date 2021/4/23
* @Version V1.0
**/
public class Client {
public static void main(String[] args) {
Student studentA = new Student("001", "张三");
Student studentB = new Student("002", "李四");
Student studentC = new Student("003", "王五");
Student studentD = new Student("004", "赵六");
Student studentE = new Student("005", "田七");
List<Student> studentList = new ArrayList<>();
studentList.add(studentB);
studentList.add(studentA);
studentList.add(studentC);
studentList.add(studentD);
studentList.add(studentE);
PeriodicalPublisher publisher = new PeriodicalPublisher();
Set<String> periodicalTypeSet = PeriodicalPublisher.getPeriodicalTypeSet();
// 学生订阅期刊
studentList.forEach(student -> {
periodicalTypeSet.forEach(periodical -> {
publisher.subscribe(periodical, student);
});
});
// 期刊发布内容
publisher.publish("科技类", "科技发展");
publisher.publish("历史类", "5000年文明历史");
publisher.publish("计算机类", "人工智能");
publisher.publish("哲学类", "马克思主义");
publisher.publish("娱乐类", "开心每一天");
// 某学生取消某类期刊
publisher.unsubscribe("娱乐类", studentC);
publisher.unsubscribe("哲学类", studentA);
// 期刊发布内容
publisher.publish("科技类", "科技发展");
publisher.publish("历史类", "5000年文明历史");
publisher.publish("计算机类", "人工智能");
publisher.publish("哲学类", "马克思主义");
publisher.publish("娱乐类", "开心每一天");
}
}
三、观察者模式在Google guava中的应用
各类信息:
/**
* @Description: 消息接口
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public interface Messge {
}
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class XMsg implements Messge{
private String title;
private String body;
public XMsg(String title, String body) {
this.title = title;
this.body = body;
}
@Override
public String toString() {
return "XMsg{" +
"title='" + title + '\'' +
", body='" + body + '\'' +
'}';
}
}
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class YMsg implements Messge{
private String title;
private String body;
public YMsg(String title, String body) {
this.title = title;
this.body = body;
}
@Override
public String toString() {
return "XMsg{" +
"title='" + title + '\'' +
", body='" + body + '\'' +
'}';
}
}
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class ZMsg implements Messge{
private String title;
private String body;
public ZMsg(String title, String body) {
this.title = title;
this.body = body;
}
@Override
public String toString() {
return "XMsg{" +
"title='" + title + '\'' +
", body='" + body + '\'' +
'}';
}
}
各观察者:
/**
* @Description: a观察者
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class AObserver {
@Subscribe
public void resMessage(XMsg event) {
System.out.println(event);
}
}
/**
* @Description: B观察者
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class BObserver {
@Subscribe
public void resMessageY(YMsg event) {
System.out.println(event);
}
@Subscribe
public void resMessageZ(ZMsg event) {
System.out.println(event);
}
}
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class ZObserver {
@Subscribe
public void resMessage(ZMsg event) {
System.out.println(event);
}
}
模拟 实现gauva EventBus
/**
* @Description: 标明观察者中的哪个函数可以接收消息
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface Subscribe {
}
/**
* @Description: ObserverAction 类用来表示 @Subscribe 注解的方法,
* 其中,target 表示观察者类,method 表示方法。它主要用在 ObserverRegistry 观察者注册表中。
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class ObserverAction {
private Object target;
private Method method;
public ObserverAction(Object target, Method method) {
this.target = Preconditions.checkNotNull(target);
this.method = method;
this.method.setAccessible(true);
}
public void execute(Object event) { // event是method方法的参数
try {
method.invoke(target, event);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* @Description: 注册表,是最复杂的一个类,框架中几乎所有的核心逻辑都在这个类中。
* 这个类大量使用了 Java 的反射语法,不过代码整体来说都不难理解,其中,一个比较有技巧的地方是 CopyOnWriteArraySet 的使用。
* CopyOnWriteArraySet,顾名思义,在写入数据的时候,会创建一个新的 set,并且将原始数据 clone 到新的 set 中,
* 在新的 set 中写入数据完成之后,再用新的 set 替换老的 set。这样就能保证在写入数据的时候,不影响数据的读取操作,以此来解决读写并发问题。除此之外,
* CopyOnWriteSet 还通过加锁的方式,避免了并发写冲突。具体的作用你可以去查看一下 CopyOnWriteSet 类的源码,一目了然。
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class ObserverRegistry {
private ConcurrentMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();
public void register(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActions = findAllObserverActions(observer);
for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActions.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> eventActions = entry.getValue();
CopyOnWriteArraySet<ObserverAction> registeredEventActions = registry.get(eventType);
if (registeredEventActions == null) {
registry.putIfAbsent(eventType, new CopyOnWriteArraySet<>());
registeredEventActions = registry.get(eventType);
}
registeredEventActions.addAll(eventActions);
}
}
public List<ObserverAction> getMatchedObserverActions(Object event) {
List<ObserverAction> matchedObservers = new ArrayList<>();
Class<?> postedEventType = event.getClass();
for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> eventActions = entry.getValue();
if (postedEventType.isAssignableFrom(eventType)) {
matchedObservers.addAll(eventActions);
}
}
return matchedObservers;
}
private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActions = new HashMap<>();
Class<?> clazz = observer.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
if (!observerActions.containsKey(eventType)) {
observerActions.put(eventType, new ArrayList<>());
}
observerActions.get(eventType).add(new ObserverAction(observer, method));
}
return observerActions;
}
private List<Method> getAnnotatedMethods(Class<?> clazz) {
List<Method> annotatedMethods = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters."
+ "Subscriber methods must have exactly 1 parameter.",
method, parameterTypes.length);
annotatedMethods.add(method);
}
}
return annotatedMethods;
}
}
/**
* @Description: EventBus 实现的是阻塞同步的观察者模式
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class EventBus {
private Executor executor;
private ObserverRegistry registry = new ObserverRegistry();
public EventBus() {
this(MoreExecutors.directExecutor());
}
protected EventBus(Executor executor) {
this.executor = executor;
}
public void register(Object object) {
registry.register(object);
}
public void post(Object event) {
List<ObserverAction> observerActions = registry.getMatchedObserverActions(event);
for (ObserverAction observerAction : observerActions) {
executor.execute(new Runnable() {
@Override
public void run() {
observerAction.execute(event);
}
});
}
}
}
/**
* @Description: 异步非阻塞
*
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class AsyncEventBus extends EventBus{
public AsyncEventBus(Executor executor) {
super(executor);
}
}
客户端测试:
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/24
* @Version V1.0
**/
public class Client {
private static EventBus eventBus;
private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 3;
public static void main(String[] args) {
eventBus = new AsyncEventBus(newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式
eventBus.register(new AObserver());
eventBus.register(new BObserver());
eventBus.register(new ZObserver());
Messge messgeX = new XMsg("科技类", "华为终端造车");
Messge messgeY = new YMsg("娱乐类", "xxx与xxx离婚");
Messge messgeZ = new ZMsg("国际类", "印度新冠肆虐");
eventBus.post(messgeX);
eventBus.post(messgeY);
eventBus.post(messgeZ);
}
}
四、观察者模式适合应用场景
- 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。
当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。
观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。
- 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。
订阅列表是动态的, 因此订阅者可随时加入或离开该列表。
五、观察者模式优缺点
- 开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
- 你可以在运行时建立对象之间的联系。
- XXX 订阅者的通知顺序是随机的。
六、与其他模式的关系
-
责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
- 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
- 命令在发送者和请求者之间建立单向连接。
- 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
- 观察者允许接收者动态地订阅或取消接收请求。
-
中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。
中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。
有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。
当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。
假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。