观察者模式在哪里应用?
经常开发的朋友都熟悉以下场景:
- 点击按钮响应一个点击事件
- 网络请求返回报文后会触发响应逻辑
- 前端要根据数据源的变化而变化
- 根据键鼠操作实时渲染新图像
从应用场景中自然地抽象出“变化源”和“响应者”两个角色,在观察者模式中我们分别称为消息的“发布者”和“订阅者”,应用观察者模式可实现一对一、一对多的“变化-响应”过程
如何得知变化源发生了变化?
很容易想到以下两种方式:
- 拉式:订阅者向发布者取信息
- 推式:发布者主动通知订阅者
结合两种方案还有先推后拉式:
- 先推后拉:发布者告知订阅者发生变化,订阅者有空时再来取走信息
拉式方案一般采用轮询方式,轮询频率过高会造成资源浪费,频率过低对变化的响应慢
推式方案则有些“懒加载”味道,仅变化时响应,兼顾性能和时效更常用
观察者模式的两个角色并不是各自独立运行的自动机,而是同一个进程中代码的执行次序控制:推式方案中,控制执行到发布者检测到变化代码后,转去执行订阅者中负责响应变化的代码
如何可复用可扩展?
为什么要关联发布者和订阅者?
既然是控制执行次序,只要每次在客户端依次调用订阅者和发布者即可,为何要搞得这么复杂?复用性!
平常写项目的时候,相同的流程和逻辑我们会抽出来另写一个函数,我们拎出来的这种共性,就是“抽象出的模式”。把发布者和订阅者之间的关系打包起来,这种共性节省了代码量,使用时也无需关注内部实现
我们想要的效果是拿来即用:使用者仅给出参与角色这套模式即可运转,而不是每次使用都要重复写两者的交互
为什么要面对抽象编程?
松耦合!即降低依赖。面向实现编程时会产生大量的依赖,如发生类名修改时,所有含有该实现类的代码都需要修改,这可是一个大工程。而面对抽象编程时,我们好像在写一个关于类之间的调用关系公式,仅当实例化对象时才使用这个公式。当实现发生修改时,我们修改的是代入公式的值,而不是公式本身
推式如何实现对变化的响应?
发布者和订阅者如何关联?
发布者要知道发生变化时通知哪个、哪些订阅者,因此需要一个 1:1 或 1:n 对应关系表
实现时要获得所有订阅者的引用(或转向执行的代码地址)
如何实现响应过程?
发布者把控制传递到订阅者响应函数的入口地址
实现时直接调用已引用订阅者的响应函数
如何把变化信息传给订阅者?
可在调用订阅者响应函数时传参过去
/** * 信息 */ public class Infomation { ... } /** * 发布者 */ public interface Publisher { List users = new Arraylist<Subscriber>; // 订阅关系列表 Infomation info = null; // 信息实体 /** * 建立订阅关系 */ void subscribe(Subscriber user) { users.add(user); } /** * 解除订阅关系 */ void unsubscribe(Subscriber user) { users.remove(user); } /** * 信息发生变化 */ void change(Information info) { this.info = info; notify(); } /** * 通知和响应过程,参数传递信息 */ void notify() { for(Subscriber s: this.users) { s.onChange(info); } } } /** * 订阅者 */ public interface Subscriber { /** * 具体响应逻辑,形参获取信息 */ void onChange(Information info) { ... } }