【一起学系列】之观察者模式:我没有在监控你啊
~
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
别名:发布-订阅模式
观察者模式的诞生
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的致性,我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。
说人话就是:
【产品】:开发小哥,我需要你设计一个天气预报显示大屏,气象站会给你发送数据,你需要把它展示到大屏里,OK吗?
【开发】:OJBK!秒秒钟搞定一切!代码立马出来!
void getTemperature () {
// 从气象站获取发送过来的温度数据
// getData();
// ................................
// 显示到大屏里面去
// showDataToScreen();
// ................................
}
void getMisture () {
// 从气象站获取发送过来的湿度数据
// getData();
// ................................
// 显示到大屏里面去
// showDataToScreen();
// ................................
}
void getAirindex () {
// 从气象站获取发送过来的空气指数数据
// getData();
// ................................
// 显示到大屏里面去
// showDataToScreen();
// ................................
}
【BOSS】:磕大头!宁是准备每获取一次数据就把代码CV一遍吗?你不累吗?
【开发】:老大,我一点都不累!就是复制粘贴一下呀!
【BOSS】:如果我现在不需要同步更新天气指数呢?删代码吗?
【开发】:对啊!一秒钟就能删掉!( •̀ ω •́ )✧
【BOSS】:重写?
HeadFirst 核心代码
于是乎,我们开启了关于设计模式的经典书籍阅读之旅
/**
* 观察主题接口
*/
public interface Observable{
public void addObserver(Observer observer); // 添加观察者
public void removeObserver(Observer observer); // 移除观察者
public void notifyObservers(WeatherData data); // 通知所有观察者
}
/**
* 观察者
*/
public interface Observer {
public abstract void update(WeatherData data);
}
/**
* 天气主题
*
*/
public class Weather implements Observable {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(WeatherData data) {
for (Observer observer : observers)
observer.update(data);
}
}
观察者模式的设计思路:
- Subject 目标(容器)提供注册和删除观察者的接口以及更新接口
- Observer(观察者)为那些在目标发生改变时需获得通知的对象定义一个更新接口
- ConcreteSubject(具体目标)状态发生改变时,向各个观察者发出通知
- ConcreteObserver(具体观察者)实现Observer的更新接口
简单来说,
- 我们需要一个接口来定义注册,删除和更新接口
- 然后由具体的目标(类)实现该接口,并且在类中创建一个容器,存储需要被通知的对象
- 需要被通知的对象,需要实现Observer接口中的update更新方法
- 将观察者对象注册进容器中,当具体目标更新时,调用所有容器类对象的update方法
❝如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面 ❞
JDK中的观察者模式
JDK中已经对观察者模式有具体的实现,代码非常简单,如下所示:
具体目标:
public class ObservableApp extends Observable {
private long curr;
public ObservableApp(long curr) {
this.curr = curr;
}
public void change(long newStr) {
this.curr = newStr;
// 更改状态,发送通知
setChanged();
notifyObservers(newStr);
}
@Override
protected synchronized void setChanged() {
super.setChanged();
}
}
具体观察者:
public class ObserverA implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println(MessageFormat.format("ObserverA -> {0} changed, Begin to Work. agr is:{1}", o.getClass().getSimpleName(), arg));
}
}
Main方法:
public class App {
public static void main(String[] args) throws InterruptedException {
ObservableApp app = new ObservableApp(System.currentTimeMillis());
System.out.println(app.getCurr());
app.addObserver(new ObserverA());
app.addObserver(new ObserverB());
Thread.sleep(1000);
long curr = System.currentTimeMillis();
app.change(curr);
System.out.println(app.getCurr());
}
}
// 输出如下:
// 1589688733464
// ObserverB -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// ObserverA -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// 1589688734469
推模式
通知发给观察者,通知携带参数,这就是推,对应JDK方法中的:notifyObservers(Object arg)
拉模式
通知发给观察者,通知不携带参数,需要观察者自己去主动调用get方法获取数据,这就是拉
对应JDK方法中的:notifyObservers(),仅告知观察者数据发生了变化,至于数据的详情需要观察者主动到主题中pull数据
拉模型强调的是目标不知道它的观察者,而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的。另一方面。拉模型可能效率较差,因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。
遵循的设计原则
-
「封装变化」
- 在观察者模式中会经常改变的是主题的状态,以及观察者的数目和类型
- 我们可以改变依赖于主题状态的对象,但是不必改变主题本身,这便是提前规划
-
「针对接口编程」
- 主题和观察者都使用了接口
- 观察者利用主题的接口向主题注册
- 主题利用观察者接口通知观察者,可以使两者之间正常交互,同时又具有松耦合的特性
-
「多使用组合」
- 观察者模式利用组合将许多观察者组合进主题中
- 它们之间的关系并不是通过继承得到,而是在运行时动态改变
什么场景适合使用
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern),比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式
Code/生活中的实际应用
- 比如微信公众号中的订阅关注,订阅后,公众号发布文章会实时分发给各个账号
- 又如,我们使用Keep跑步时,如果你跑的足够激情,它会提示你,恭喜你,你已经打破了五公里的最好记录!这样的语音提醒一定是触发式,而不是实时去检测吧?(实时检测没有意义,浪费性能)这里就可以利用观察者模式进行设计和解耦
最后
「附上GOF一书中对于观察者模式的UML图:」
观察者模式UML图
相关代码链接
GitHub地址:https://github.com/kkzhilu/Kerwin-DesignPattern
- 兼顾了《HeadFirst》以及《GOF》两本经典数据中的案例
- 提供了友好的阅读指导
- Cookie、Session登陆验证相关介绍和用法
- 题型分析
- .NET CORE 框架ABP的代码生成器(ABP Code Power Tools )使用说明文档
- Lua编写wireshark插件初探——解析Websocket上的MQTT协议
- 在Ubuntu 16.04环境下安装Docker-CE(附视频教程)
- 修改HTML5 input placeholder 颜色及修改失效的解决办法
- 设置同样字体大小,chrome浏览器有时字体偏大的解决办法(转)
- 手机端调用系统相册并上传图片
- select自定义小三角样式
- 一个非常好用的文字滚动的案例,鼠标悬浮可暂停
- localstorage和sessionstorage上手使用记录
- jquery升级到新版本报错[jQuery] Cannot read property ‘msie’ of undefined错误的解决方法(转)
- thinphp框架的项目svn重新检出后的必备配置
- 对事件委托绑定click的事件的解绑
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法