深入浅出观察者模式

观察者模式在哪里应用?

经常开发的朋友都熟悉以下场景:

  • 点击按钮响应一个点击事件
  • 网络请求返回报文后会触发响应逻辑
  • 前端要根据数据源的变化而变化
  • 根据键鼠操作实时渲染新图像

从应用场景中自然地抽象出“变化源”和“响应者”两个角色,在观察者模式中我们分别称为消息的“发布者”和“订阅者”,应用观察者模式可实现一对一、一对多的“变化-响应”过程

如何得知变化源发生了变化?

很容易想到以下两种方式:

  • 拉式:订阅者向发布者取信息
  • 推式:发布者主动通知订阅者

结合两种方案还有先推后拉式:

  • 先推后拉:发布者告知订阅者发生变化,订阅者有空时再来取走信息

拉式方案一般采用轮询方式,轮询频率过高会造成资源浪费,频率过低对变化的响应慢

推式方案则有些“懒加载”味道,仅变化时响应,兼顾性能和时效更常用

观察者模式的两个角色并不是各自独立运行的自动机,而是同一个进程中代码的执行次序控制:推式方案中,控制执行到发布者检测到变化代码后,转去执行订阅者中负责响应变化的代码

如何可复用可扩展?

为什么要关联发布者和订阅者?

既然是控制执行次序,只要每次在客户端依次调用订阅者和发布者即可,为何要搞得这么复杂?复用性!

平常写项目的时候,相同的流程和逻辑我们会抽出来另写一个函数,我们拎出来的这种共性,就是“抽象出的模式”。把发布者和订阅者之间的关系打包起来,这种共性节省了代码量,使用时也无需关注内部实现

我们想要的效果是拿来即用:使用者仅给出参与角色这套模式即可运转,而不是每次使用都要重复写两者的交互

为什么要面对抽象编程?

松耦合!即降低依赖。面向实现编程时会产生大量的依赖,如发生类名修改时,所有含有该实现类的代码都需要修改,这可是一个大工程。而面对抽象编程时,我们好像在写一个关于类之间的调用关系公式,仅当实例化对象时才使用这个公式。当实现发生修改时,我们修改的是代入公式的值,而不是公式本身

推式如何实现对变化的响应?

发布者和订阅者如何关联?

发布者要知道发生变化时通知哪个、哪些订阅者,因此需要一个 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) { ... }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

©2018-2024 Howell版权所有 备案号:冀ICP备19000576号