设计模式-观察者模式(observer)
设计模式-观察者模式(observer)
概要
记忆关键词:通知、自动更新
定义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
分析:观察者模式又叫做发布-订阅(Publish/Subscribe)模式。观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。这其实是依赖倒置原则的最佳体现。
类型:行为型
观察者模式结构图:
一、能解决什么问题
观察者模式适用于需要事件通知的场景。通过使用观察者模式,当某个事件发生时,主题对象会通知所有注册的观察者,观察者可以根据需要对事件做出反应。这种机制广泛应用于 GUI 框架、事件驱动编程和实时系统中。
二、涉及的相关角色
1. Subject 被观察者
1 abstract class Subject { 2 private List<Observer> obs = new ArrayList<>(); 3 4 public void addObserver(Observer obs){ 5 this.obs.add(obs); 6 } 7 public void delObserver(Observer obs){ 8 this.obs.remove(obs); 9 } 10 protected void notifyObserver(){ 11 for(Observer o: obs){ 12 o.update(); 13 } 14 } 15 public abstract void doSomething(); 16 }
2. ConcreteSubject 具体被观察者
1 class ConcreteSubject extends Subject { 2 public void doSomething(){ 3 System.out.println("被观察者事件发生改变"); 4 this.notifyObserver(); 5 } 6 }
3. Observer类 观察者
1 interface Observer { 4 public void update(); 8 }
4. Concreteobserver类 具体观察者
1 class ConcreteObserver1 implements Observer { 2 public void update() { 3 System.out.println("观察者1收到信息,并进行处理"); 4 } 5 } 6 class ConcreteObserver2 implements Observer { 7 public void update() { 8 System.out.println("观察者2收到信息,并进行处理"); 9 } 10 }
5. 客户端
1 public class Client { 2 public static void main(String[] args){ 3 Subject sub = new ConcreteSubject(); 4 sub.addObserver(new ConcreteObserver1()); //添加观察者1 5 sub.addObserver(new ConcreteObserver2()); //添加观察者2 6 sub.doSomething(); 7 } 8 } 9 10 //运行结果 11 被观察者事件发生改变 12 观察者1收到信息,并进行处理 13 观察者2收到信息,并进行处理
三、推模型和拉模型
1. 推模型
在推模型中,当主题(Subject)发生变化时,它会主动将更新的数据或信息推送给所有的观察者(Observer)。每个观察者在接收到通知时都能直接获得更新的信息或数据。
特点:
-
主动推送:主题主动推送更新给观察者。
-
简化观察者逻辑:观察者不需要请求数据,数据已由主题提供。
-
适用于数据量较小或更新频率较低的场景:由于每次通知都会推送完整的数据或信息,适合数据量小且更新频率不高的场景。
代码示例:
1 interface Observer { 2 void update(String data); 3 } 4 5 class ConcreteSubject { 6 private List<Observer> observers = new ArrayList<>(); 7 private String data; 8 9 public void registerObserver(Observer observer) { 10 observers.add(observer); 11 } 12 13 public void notifyObservers() { 14 for (Observer observer : observers) { 15 observer.update(data); // Push model: provide the data directly 16 } 17 } 18 19 public void setData(String data) { 20 this.data = data; 21 notifyObservers(); 22 } 23 }
2. 拉模型
在拉模型中,当主题发生变化时,它会通知观察者有更新,但不直接提供数据。观察者需要主动请求主题获取更新的数据或信息。观察者在接收到通知后,通常会调用主题的方法以获取最新的数据。
特点:
- 被动拉取:观察者主动拉取(请求)更新的数据。
- 更灵活:观察者可以根据需要选择是否拉取数据,或者以不同的方式处理数据。
- 适用于数据量较大或更新频率较高的场景:观察者只在需要时拉取数据,有助于减少不必要的数据传输。
代码示例:
1 interface Observer { 2 void update(ConcreteSubject subject); 3 } 4 5 class ConcreteSubject { 6 private List<Observer> observers = new ArrayList<>(); 7 private String data; 8 9 public void registerObserver(Observer observer) { 10 observers.add(observer); 11 } 12 13 public void notifyObservers() { 14 for (Observer observer : observers) { 15 observer.update(this); // Pull model: notify observers to pull data 16 } 17 } 18 19 public void setData(String data) { 20 this.data = data; 21 notifyObservers(); 22 } 23 24 public String getData() { 25 return data; 26 } 27 } 28 29 class ConcreteObserver implements Observer { 30 @Override 31 public void update(ConcreteSubject subject) { 32 String data = subject.getData(); // Pull the data from the subject 33 System.out.println("Received data: " + data); 34 } 35 }
四、什么时候使用观察者模式?
1. 当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
2. 当一个抽象模型有两方面,其中一方面依赖另一方面,这时用观察者模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。
比如下面这些情况下:
1)订单支付成功后,会做各种动作,比如发送EMAIL,或者改变订单状态,发送短信给客户,修改优惠券,通知仓库订单号等等
2)网站登录之后,修改登录时间,推送最新活动,让附近的人给他打招呼
五、应用场景
1. JDK中的观察者模式
观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 JDK 对观察者模式的支持。but,在 Java9 被弃用了。
java.util.Observer接口
1 package java.util; 2 3 public interface Observer { 4 void update(Observable o, Object arg); 5 }
java.util.Observable类
1 import java.util.Observer; 2 import java.util.Vector; 3 4 /** 5 * 这里其实就是主题接口的角色,只不过 JDK 的实现很烂——竟然用类封装的,这是公认的槽点之一。 6 */ 7 public class Observable { 8 private boolean changed = false; 9 10 // 看到这里,其实也知道,这个API不仅太古老,而且还没人维护了,用的还是最老的,被淘汰的 Vector 实现的动态数组 11 private Vector obs; 12 13 public Observable() { 14 obs = new Vector(); 15 } 16 17 // 注册观察者,线程安全,这是优点之一,可以借鉴 18 public synchronized void addObserver(Observer o) { 19 if (o == null) // 提高代码健壮性 20 throw new NullPointerException(); 21 if (!obs.contains(o)) { // 注册时会去重,自定义实现需要注意也去重 22 obs.addElement(o); 23 } 24 } 25 26 // 观察者取消注册 27 public synchronized void deleteObserver(Observer o) { 28 obs.removeElement(o); 29 } 30 31 // 基于拉模型的通知方法 32 public void notifyObservers() { 33 notifyObservers(null); 34 } 35 36 // 基于推模型 37 public void notifyObservers(Object arg) { 38 // 一个临时数组,用于并发访问被观察者时,保存观察者列表的当前状态——这就是基于备忘录模式的简单应用。 39 Object[] arrLocal; 40 // 在获取到观察者列表之前,不允许其他线程改变观察者列表 41 synchronized (this) { 42 if (!changed) 43 return; 44 arrLocal = obs.toArray(); 45 // 重置变化标记位为 false 46 clearChanged(); 47 } 48 49 // 主题类释放锁,但是并不影响线程安全,因为加锁之前已经将观察者列表复制到临时数组 arrLocal 50 // 在通知时我们只通知数组中的观察者,当前删除和添加观察者,都不会影响我们通知的对象 51 for (int i = arrLocal.length - 1; i >= 0; i--) 52 ((Observer) arrLocal[i]).update(this, arg); 53 } 54 55 public synchronized void deleteObservers() { 56 obs.removeAllElements(); 57 } 58 59 protected synchronized void setChanged() { 60 changed = true; 61 } 62 63 protected synchronized void clearChanged() { 64 changed = false; 65 } 66 67 public synchronized boolean hasChanged() { 68 return changed; 69 } 70 71 public synchronized int countObservers() { 72 return obs.size(); 73 } 74 }
2. Spring 中的观察者模式
具体介绍请参考文章《Spring - 事件驱动模型》
参考链接:
https://www.cnblogs.com/study-hard-forever/p/13167161.html
https://juejin.cn/post/6844904100459446285