观察者模式案例的简单分析

观察者模式

简介

观察者模式也叫发布-订阅模式,其定义如下:定义对象间一种一对多的依赖关系,使得当该对象状态改变时,所有依赖于它的对象都会得到通知,并被自动更新。

观察者模式的通知方式可以通过直接调用等同步方式实现(如函数调用,HTTP接口调用等),也可以通过消息队列异步调用(同步调用指被观察者发布消息后,必须等所有观察者响应结束后才可以进行接下来的操作;异步调用指被观察者发布消息后,即可进行接下来的操作)。

简单来说,就是在软件设计中维护一个依赖列表,当任何状态发生改变自动通知他们。借用一个在博客看到的例子,观察者模式就像报纸和杂志的订阅:向某家报社订阅报纸,只要报社有新的的报纸出版,就会给你送来,只要你是他们的客户,你就会一直收到新的报纸。当你不想再看报纸的时候,可以取消订阅,他们就不再送新的报纸过来。

只要报社还一直在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。

特点

对于一个大型复杂的系统,观察者模式可以很好的解决类间消息交换的问题。总结来说,观察者模式有以下优点:

  • 观察者和被观察者之间是抽象耦合的;
  • 可以把许多负责单一职责的模块进行触发,也可以很方便的实现广播。

观察者模式的优点有时候也会是缺点,他可能会带来整体系统效率的浪费,同时如果被观察者之间有依赖关系的话,其逻辑关系的梳理需要费一些心思。

案例分析

我在github上看到了一个同学做的例子,觉得还不错:一个模块加载器的简单实现

这个案例实现了一个简单的模块加载器,其中就用到了观察者模式。接下来我们分析一下其中用到的观察者模式。

对于Watcher这个构造函数,task是一个待执行的callback,uris是这个异步callback所依赖的模块(地址),dep是一个订阅器。$len是依赖模块的数组长度。如果一个模块加载好了,那么通知这个Watcher,这个Watcher的$len变量就减一。对于一个Watcher,我们不用关心当前到底是哪个模块加载好了,反正只能是所有依赖模块加载好,这个task才能被执行。所以当$len为零的时候,表面依赖全部加载好,那么这个Wathcer就执行这个task。

function Watcher(task, uris, dep, Module){
    this.$task = task;
    this.$uris = uris;
    this.dep = dep;
    this.$Module = Module;
    this.modArr = [];
    this.$len = this.$uris.length;
}

Watcher每执行一次update,this.$len--。当为零的时候,执行this.run()方法。this.run()中,如果task是一个函数,那么执行执行。因为在define函数中,如果define里面没有依赖,就会将其callback直接放入Watcher。如果有依赖,则会先创建一个task对象,将当前define脚本的src存入task,以便触发该dep的notify方法。

Watcher.prototype = {
    update: function () {
        this.$len--;
        if (this.$len <= 0) {
            this.run();
        }
    },

    run: function () {
        let mod = this.$Module.module,
            task = this.$task;

        this.$uris.forEach(uri => {
            this.modArr.push(mod[uri].obj);
        });
        //this.$Module.module[this.dep.depName].obj =
        if (typeof task == 'function') {
            task.apply(null, this.modArr);
            return
        }
        let src = task.currentSrc;
        mod[src].obj = task.callback.apply(null, this.modArr);
        mod[src].dep.notify();
        this.dep.removeSub(this);
        return
    }
};

Dep是一个订阅器,用一个订阅器来存放一个模块,不管define有多深,模块a依赖于模块b,模块b依赖于模块c。当模块c加载好后(约定模块c是不依赖于任何其他模块的),模块c的订阅器dep触发notify方法,subs里面的Watcher的update方法。

function Dep(depName){
    this.id = uid++;
    this.subs = [];
    this.depName = depName;
}

Dep.prototype = {
    /**
     * 添加订阅, 将watcher添加进数组subs
     * @param {Object} task new watcher()
     */
    addSubs: function(task){
        this.subs.push(task);
    },
    /**
     * 删除订阅, 将watcher从数组subs中删除
     * @param {Object} task new watcher()
     */
    removeSub: function(task){
        let index = this.subs.indexOf(task);
        (index != -1) && this.subs.splice(index, 1);
    },
    /**
     * 当该模块加载好的时候, 通知所有依赖它的task
     */
    notify: function(){
        this.subs.forEach(task => {
            task.update();
        });
    }
};

以上就实现了一个简单的模块加载器。一个对象的改变状态,则所有依赖于它的对象都会得到通知更新。在这个例子中Watcher跟Dep之间是关联关系,是抽象耦合的,它们之间形成一条触发链,依次对每一个Dep中的对象进行处理。这样就很好的解决了对象之间的通信问题。

posted @ 2018-10-24 12:53  luuu  阅读(1028)  评论(0编辑  收藏  举报