1 描述
- 也常被叫作订阅-发布模式/生产者-消费者/事件发布-事件监听。
- 观察者模式在现代的软件开发中应用非常广泛,比如,商品系统、物流系统、监控系统、运营数据分析系统等
2 模式原理分析
2.1 定义
- 定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。
- 两个前提条件
- 被依赖的对象叫作被观察者,依赖的对象叫作观察者
- 观察者观察被观察者的状态变化
- 两个前提条件
3 使用场景
- 当一个对象状态的改变需要改变其他对象时。
- 比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
- 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。
- 比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
- 需要创建一种链式触发机制时。
- 比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
- 微博或微信朋友圈发送的场景。
- 这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
- 需要建立基于事件触发的场景。
- 比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。
4 为什么使用观察者模式
-
1 为了方便捕获观察对象的变化并及时做出相应的操作
- 观察者模式对于对象自身属性或行为发生变化后,需要快速应对的应用场景尤其有效
-
2 为了提升代码扩展性
- 用观察者模式则只需要将观察者对象注册到被观察者存储的观察者列表中就能完成代码扩展。
5 优缺点
5.1 优点
- 能够降低系统与系统之间的耦合性
- 建立以事件驱动的系统,我们能创建更多观察者与被观察者,对象之间相互的关系比较清晰,可以随时独立添加或删除观察者
- 提升代码扩展性
- 由于观察者和被观察之间是抽象耦合,所以增删具体类并不影响抽象类之间的关系,这样不仅满足开闭原则,也满足里氏替换原则,能够很好地提升代码扩展性
- 可以建立一套基于目标对象特定操作或数据的触发机制
- 基于消息的通知系统、性能监控系统、用户数据跟踪系统等,观察者通过特定的事件触发或捕获特定的操作来完成一系列的操作
5.2 缺点
- 增加代码的理解难度
- 降低了系统性能
- 观察者模式通常需要事件触发,当观察者对象越多时,被观察者需要通知观察者所花费的时间也会越长,这样会在某种程度上影响程序的效率
6 总结
-
当我们在使用观察者模式时,要重点关注哪些变化是我们需要从被观察对象那里捕获,并进行下一步处理的
- 不能盲目捕获所有的变化
- 不能遗漏重要的变化
-
找到合适的变化并进行正确的处理才是使用观察者模式的正确打开方式。
6 代码
6.1 jdk 支持
- 实现大致思路
- 就是 Observable Vector<Observer>记录 观察者, 当被观察者改变了, 就调用观察者 update。
- Observable 被观察者
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
}
- Observer 观察者
public interface Observer {
void update(Observable o, Object arg);
}
6.2 模拟apollo 实现 一个配置文件改变,就改变读取了这个配置文件的客户端。
- 观察者模式UML
- 代码
- DataSourcesCache 类模拟定时读取配置文件
/**
* Data class
* 配置文件实体
* @date 2021/8/17
*/
@Accessors(chain = true)
@lombok.Data
public class Data {
private Long id;
private String name;
private String data;
}
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.Map;
import java.util.Observable;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* DataSourcesCache class
* 模拟定时读取某个文件,监控是否变化。
* @date 2021/8/17
*/
@Slf4j
public class DataSourcesCache extends Observable {
public static Map<String,Data> cache = new ConcurrentHashMap<>();
public void init(){
ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("DataSourcesCache-%s").daemon(true).build();
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2 , threadFactory);
scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
Random random = new Random();
Long i = random.nextLong();
Data data = new Data();
data.setId(i).setName("name" + i).setData("data" + i);
String key = random.nextInt(2) == 1 ? "gelin" : "gelin1" ;
cache.put(key,data);
super.setChanged();
super.notifyObservers(key);
log.info("change {} data {}",key, JSON.toJSONString(data));
},5,5, TimeUnit.SECONDS);
}
}
- DataSourcesClient 客户端
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Observable;
import java.util.Observer;
/**
* DataSourcesClient class
* 客户端读取配置文件
* @date 2021/8/17
*/
@Data
@Slf4j
public class DataSourcesClient implements Observer {
private String name;
private com.designPatterns.observer.apollo.Data data;
@Override
public void update(Observable o, Object arg) {
if(arg instanceof String) {
String key = (String) arg;
if(key.equals(name)){
data = DataSourcesCache.cache.get(arg);
log.info("name: {}, change : {}", name,JSON.toJSONString(data));
}
}
}
}
- Main 主程序
public class Main {
public static void main(String[] args) {
// 配置数据来源 通过定时刷新
DataSourcesCache dataSourcesCache = new DataSourcesCache();
dataSourcesCache.init();
DataSourcesClient dataSourcesClient = new DataSourcesClient();
dataSourcesClient.setName("gelin");
DataSourcesClient dataSourcesClient2 = new DataSourcesClient();
dataSourcesClient2.setName("gelin1");
dataSourcesCache.addObserver(dataSourcesClient);
dataSourcesCache.addObserver(dataSourcesClient2);
synchronized (Main.class) {
try {
Main.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 输出
16:57:44.754 [DataSourcesCache-1] INFO com.designPatterns.observer.apollo.DataSourcesClient - name: gelin1, change : {"data":"data-7371821606940861810","id":-7371821606940861810,"name":"name-7371821606940861810"}
16:57:44.757 [DataSourcesCache-1] INFO com.designPatterns.observer.apollo.DataSourcesCache - change gelin1 data {"data":"data-7371821606940861810","id":-7371821606940861810,"name":"name-7371821606940861810"}
16:57:49.763 [DataSourcesCache-1] INFO com.designPatterns.observer.apollo.DataSourcesClient - name: gelin1, change : {"data":"data-5749414509503057278","id":-5749414509503057278,"name":"name-5749414509503057278"}
16:57:49.763 [DataSourcesCache-1] INFO com.designPatterns.observer.apollo.DataSourcesCache - change gelin1 data {"data":"data-5749414509503057278","id":-5749414509503057278,"name":"name-5749414509503057278"}
16:57:54.769 [DataSourcesCache-2] INFO com.designPatterns.observer.apollo.DataSourcesClient - name: gelin, change : {"data":"data-6057120240679674117","id":-6057120240679674117,"name":"name-6057120240679674117"}
16:57:54.769 [DataSourcesCache-2] INFO com.designPatterns.observer.apollo.DataSourcesCache - change gelin data {"data":"data-6057120240679674117","id":-6057120240679674117,"name":"name-6057120240679674117"}
参考
- 拉钩教育 趣学设计模式
网友评论