【设计模式】- 观察者模式

2,101 阅读7分钟

目录

  1. 观察者模式
  2. 观察者模式-脑图分析
  3. 参考
  4. 总结
  5. 场景
  6. 观察者模式 和 订阅发布模式的区别
  7. 观察者模式 - 完整版本

备注:后续的 举例子:如下:Aclass为目标对象Bclass为观察者

一、观察者模式

在 Subject 对象添加了一系列 Observer 对象之后,Subject 对象则维持着这一系列 Observer 对象,当有关状态发生变更时 Subject 对象则会通知这一系列 Observer 对象进行更新

观察者模式与发布订阅模式相比,耦合度更高,通常用来实现一些响应式的效果。在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

  • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
  • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

按照这种定义,我们可以实现一个简单版本的观察者模式。

// 观察者
class Observer {
    /**
     * 构造器
     * @param {Function} cb 回调函数,收到目标对象通知时执行
     */
    constructor(cb){
        if (typeof cb === 'function') {
            this.cb = cb
        } else {
            throw new Error('Observer构造器必须传入函数类型!')
        }
    }
    /**
     * 被目标对象通知时执行
     */
    update() {
        console.log('Observer update', 'this.cb', this.cb.toString());
        this.cb()
    }
}

// 被观察者(目标对象)
class Subject {
    constructor() {
        // 维护观察者列表 `Aclass中存的  [Bclass, Bclass, Bclass ...]`
        this.observerList = [];
        
    }
    /**
     * 添加一个观察者 `Aclass.add(Bclass)` 
     * @param {Observer} observer Observer实例
     */
    addObserver(observer) {
        console.log('添加一个观察者addObserver',observer);
        this.observerList.push(observer);
        console.log('维护观察者列表 this.observerList', this.observerList);
    }
    /**
     * 通知所有的观察者  `Aclass.notify` -> `Bcalss.update` -> `Bcalss.cb`
     */
    notify() {
        this.observerList.forEach(observer => {
            console.log('Subject notify');
            observer.update()
        })
    }
}

const observerCallback = function() {
    console.log('observerCallback 我被通知了')
}
const observer = new Observer(observerCallback)
console.log('observer实例', observer);
const subject = new Subject();
console.log('subject实例', subject);
subject.addObserver(observer);
subject.notify();

二、观察者模式-脑图分析

1) 观察者类

image.png

2) 被观察者类(目标对象)

image.png

3) 实例化 调用

image.png

image.png

3) 调用 流程

// 声明两个观察者回调,也就是最终要执行的函数
var observerCb1 = function() {
    console.log('observer  Cb1')
}
var observerCb2 = function() {
    console.log('observer  Cb2')
}

// 实例化 两个观察者
var observer1 = new Observer(observerCb1)
var observer2 = new Observer(observerCb2)

// 实例化 目标对象
const subject = new Subject();

// 往目标对象添加观察者实例
subject.addObserver(observer1);
subject.addObserver(observer2);
// 通知所有观察者 调用其观察者回调
subject.notify();

// 最终输出
// observer  Cb1
// observer  Cb2

image.png

4) 调用过程

image.png

5) 红色为关键调用

image.png

三、 参考

四、 总结

  • 【观察者模式】调用的就是在外面定义的 观察者回调们

  • 【观察者模式】关键是往实例化的目标对象 传入 观察者实例们,从而 实例化的目标对象拥有了 所有的观察者的属性和方法,从而可以调用观察者们对应的自已的定义在外部的观察者回调们。

  • 【观察者模式】目标对象的构造器数组中,存放的是所有的观察者实例们。

  • 这个目标对象也就是被观察者类的实例 可以 **添加另外一个类的实例** -> 自己维护一个观察者实例列表 -> 在去遍历这个列表,调用各个观察者实例原型上的 update方法 -> 从而间接的调用了,观察者实例上存储的传入的观察者回调函数

  • 一个类的实例 可以添加另一个类的实例并存储起来调用另外一个类传入的参数 (观察者回调 也就是 存储在其构造器上的cb)

  • 举例子:如下:Aclass为目标对象Bclass为观察者

  • Aclass.add(Bclass)

  • Aclass.notify() 实际为 调用了 Bclass.prototype.update update中调用的是 Bclass构造器中的cb,也就是传入的观察者回调。

  • 实例化后A类添加B类到A类的观察者列表数组中: Aclass.add(Bclass) -> Aclass.prototype.observerList = [{cb: ()=>{'最终要执行的观察者回调'}, update:fn}, {cb: ()=>{'最终要执行的观察者回调'}, update:fn}, ...] -> Acalss通知所有观察者列表中的观察者 -> Bclass.cb() 调用观察者上的callback

  • 简易调用流程:Aclass.add(Bclass) -> Aclass中存的[Bclass, Bclass, Bclass ...] -> Aclass.notify -> Bcalss.update -> Bcalss.cb

  • 简易思路:观察者实例和观察者实例回调们准备好 -> 目标对象添加这些观察者实例们到 观察者列表中 -> 目标对象在某种时机去遍历调用所有的观察者实例中传入的观察者回调们

  • 附上原始脑图 www.processon.com/view/link/6… www.processon.com/diagraming/…

五、 场景

1)Vue2的数据响应式

依赖收集: get时每一个data上的属性都会维护一个dep数组,去存放所有的观察者实例们。 数据更新到视图更新: 当数据改变时,set,去调用目标对象上的notify,然后遍历目标对象观察者列表中的所有观察者们,然后,调用观察者实例上的观察者回调们。

2 )浏览器的事件监听addEventLister

六、 观察者模式 和 订阅发布模式 的区别

记住一个关键点

【观察者模式】,是维护在目标对象构造器中一个 维护观察者列表 this.observerList = [Bclass, Bclass, Bclass ...]

【观察者模式】是两个类来实现。一个观察者类,一个目标对象类。目标对象类实例添加 观察者类实例,然后在去通知notify,遍历调用所有存储在目标对象数组中的观察者回调们。

【订阅发布模式】, 是维护一个对象,存放所有的 事件与对应此事件的事件回调 {'事件a': [cb1, cb2,cb3...], '事件b': [cb1, cb2,cb3...], ...}

【订阅发布模式】是一个类来实现。是一个类的构造器上存放一个对象,用来存储事件和事件对应的回调们。然后,在原型上,有订阅事件on(事件, 回调), 触发事件emit(事件), 触发一次的事件 once(事件), 删除事件off(事件)。

【观察者模式】是两个类来实现.【订阅发布模式】是一个类来实现

【观察者模式】是一对多的关系,而且两个类是藕合在一起的.

【订阅发布模式】是你订阅你的事件,我发布我的事件。互不影响。你只管订阅了,后续就不需要在关注。后续只要有发布的事件被触发。那你就可以收到。

[订阅发布模式】的场景,微信公众号。

image.png

回顾

  • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。

  • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

  • 观察者模式与发布订阅模式相比,耦合度更高,通常用来实现一些响应式的效果。在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

  • 观察者模式定义了对象之间一对对多的依赖关系,当一个对象改变了状态,它的所有依赖会被通知,然后自动更新。

七 观察者模式 - 完整版本

观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。

在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。

在 Subject 对象添加了一系列 Observer 对象之后,Subject 对象则维持着这一系列 Observer 对象,当有关状态发生变更时 Subject 对象则会通知这一系列 Observer 对象进行更新。

// Subject 对象
function Subject(){
  this.observers = [];
}
Subject.prototype = {
  add(observer){  // 添加
    this.observers.push(observer);
  },
  notify(){  // 通知
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      observers[i].update();
    }
  },
  remove(observer){  // 删除
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      if(observers[i] === observer){
        observers.splice(i,1);
      }
    }
  },
}

// Observer 对象
function Observer(name){
  this.name = name;
}
Observer.prototype = {
  update(){  // 更新
    console.log('my name is '+this.name);
  }
}

var sub = new Subject();
var obs1 = new Observer('ob cb1');
var obs2 = new Observer('ob cb2');
sub.add(obs1);
sub.add(obs2);
sub.notify();  //my name is ob cb1、my name is ob cb2

上述代码中,我们创建了 Subject 对象和两个 Observer 对象,当有关状态发生变更时则通过 Subject 对象的 notify 方法通知这两个 Observer 对象,这两个 Observer 对象通过 update 方法进行更新。

在 Subject 对象添加了一系列 Observer 对象之后,还可以通过 remove 方法移除某个 Observer 对象对它的依赖。

var sub = new Subject();
var obs1 = new Observer('ob cb1');
var obs2 = new Observer('ob cb2');
sub.add(obs1);
sub.add(obs2);
sub.remove(obs2);
sub.notify();  //my name is ob cb1