发布订阅模式 代码设计模式

    Publishers + Subscribers = Observer Pattern ?
    _
    所谓观察者模式,其实就是为了实现松耦合(loosely coupled)

    用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,changed()方法就会被调用,于是我们可以在changed()方法里面,更新气象仪器上的数据,比如温度、气压等等。

    js原生的dom一级事件池,以及dom2事件池

    但是这样写有个问题,就是如果以后我们想在changed()方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()方法的代码,这就是紧耦合的坏处。

    怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。
    **
    观察者模式里面,changed()方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了:

    Publish–Subscribe Pattern - 图1

    DOM 二级事件
    window.addEventListener(‘scroll’, debounceTask)

    1. // 8. 事件总线 | 发布订阅模式
    2. class EventEmitter {
    3. constructor() {
    4. this.cache = {}
    5. }
    6. // 向事件池中去添加属性名为“name”订阅事件集合
    7. // 严谨一点需要首先判断订阅事件内部都要是函数,且不能重名
    8. on(name, fn) {
    9. if (this.cache[name]) {
    10. // 属性非空的情况下可以一定是数组
    11. const repeat = this.cache[name].some(f => return f === fn)
    12. // 数组去重同名事件不可以添加两次
    13. !repeat ? this.cache[name].push(fn) : null
    14. } else {
    15. // 如果为空的话直接数组化
    16. this.cache[name] = [fn]
    17. }
    18. }
    19. // 事件池事件注销
    20. off(name, fn) {
    21. // 获取名为name的订阅事件集合,不为空
    22. const tasks = this.cache[name]
    23. if (tasks) {
    24. // 寻找取消订阅事件的索引,找到对应的索引
    25. const index = tasks.findIndex((f) => f === fn || f.callback === fn)
    26. if (index >= 0) {
    27. // tasks.splice(index, 1) 可能会导致数组塌陷
    28. tasks[index] = null;
    29. }
    30. }
    31. }
    32. // 执行name订阅事件
    33. emit(name) {
    34. if (this.cache[name]) {
    35. // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
    36. const tasks = this.cache[name].slice()
    37. for (let i=0; i<tasks.length ; i++) {
    38. if( typeof tasks[i] !== "function" ){
    39. tasks.splice(i, 1)
    40. i--
    41. continue
    42. }
    43. tasks[i]();
    44. }
    45. }
    46. }
    47. emit(name, once = false) {
    48. if (this.cache[name]) {
    49. // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
    50. const tasks = this.cache[name].slice()
    51. for (let i=0; i<tasks.length ; i++) {
    52. if( typeof tasks[i] !== "function" ){
    53. tasks.splice(i, 1)
    54. i--
    55. continue
    56. }
    57. tasks[i]();
    58. }
    59. if (once) {
    60. delete this.cache[name]
    61. }
    62. }
    63. }
    64. }
    65. // 测试
    66. const eventBus = new EventEmitter()
    67. const task1 = () => { console.log('task1'); }
    68. const task2 = () => { console.log('task2'); }
    69. eventBus.on('task', task1)
    70. eventBus.on('task', task2)
    71. setTimeout(() => {
    72. eventBus.emit('task')
    73. }, 1000)
    74. // 内部可能有事件坍塌事件集合

    从表面上看:

    • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
    • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker

    往更深层次讲:

    • 观察者和被观察者,是松耦合的关系
    • 发布者和订阅者,则完全不存在耦合

    从使用层面上讲:

    • 观察者模式,多用于单个应用内部
    • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

    快递员上门送货,后来快递太多了,为了增加效率,分工更明确一点,现在多了个中间站,菜鸟驿站,快递员方便了,这是在规模起来以后自然而然的选择。
    现在是人主动去拿快递,如果以后连这也嫌弃效率不高,怎么办?
    再加一层,菜鸟驿站派出机器人送,