换一种方式理解观察者模式

观察者模式

观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。

使用观察者模式的好处:

  • 支持简单的广播通信,自动通知所有已经订阅过的对象。
  • 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
  • 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。

理解的第一阶段

我们先用一个通俗的情景来比喻一下,就拿 航班 与 航站楼 之间的关系:
《换一种方式理解观察者模式》

图上显示的是 飞机1在从 北京 飞往 南京, 北京航站楼A 和 南京航站楼B 都需要关注 飞机1 的信息,
而 飞机1 在位置变化 之后也要及时的 通知 北京航站楼A 和 南京航站楼。
(其实可以看出来,这里 航站楼是被动的接受 飞机是主动的发出变化)

所以,我们可以写一个简单的 观察/订阅模式

function Observer () {
    // 数组存放多个观察者
    this.fns = [];
}

Observer.prototype = {
    // 订阅  (飞机信息)
    sub: function (fn) {
        this.fns.push(fn);
    },
    // 退订 (飞机信息)
    unsub: function (fn) {
        this.fns = this.fns.filter(function(el) {
            if (el !== fn) {
                return el;
            }
        })
    },
    // 发布 (飞机信息变化时)
    publish: function (o, thisObj) {
        var scope = thisObj || window;
        this.fns.forEach(function(el) {
            el.call(scope, o);
        });
    }
}
var ob = new Observer;

初始化 飞机1 的地理位置

var location = '北京';

定义 2 个航站楼的行为

// 北京 航站楼A - 观察者
var hangzhanlouBeijing = function (data) {
    console.log('北京航站楼 收到了数据变化的通知,我知道现在 飞机1 的位置了,是在:' +data);
    // 做一些操作
};

// 南京 航站楼B - 观察者
var hangzhanlouNanjing= function (data) {
    console.log('南京航站楼 收到了数据变化的通知,我知道现在 飞机1 的位置了,是在:' +data + ',我这就准备接机');
    // 做一些操作
};

南京和北京航站楼开始订阅(注册),将要关注 飞机1 的地址位置

// 订阅  (飞机地理位置的变化)
ob.sub(hangzhanlouBeijing);
ob.sub(hangzhanlouNanjing);

飞机1 经过一段时间的飞行 从 北京 到了 南京 ,这时候地理位置发生了变化

// 经过一些 操作 导致 location 发生了变化
location = '南京';

重要 然后 检查(校验) location(地址位置) 是否发生变化,如果发生变化,就进行通知

// 数据 location 发生变化(现在的值 不等于 原来的值)
if (location!== '北京') {
    // 发布  数据变化了(确认地址位置变了 ),广播 所有订阅(关注) 飞机1 的观察者(航站楼)
    ob.update(location);
}

输出

北京航站楼 收到了数据变化的通知,我知道现在 飞机1 的位置了,是在: 南京
南京航站楼 收到了数据变化的通知,我知道现在 飞机1 的位置了,是在: 南京,我这就准备接机

理解的第二阶段

《换一种方式理解观察者模式》

  • 情况一:
    因为这里我们只有一架 飞机1 具有了 观察者模式,如果再来一架 飞机2 也是从 北京 飞往 南京 的呢?很明显,我们也需要知道 飞机2 的地理位置信息
    《换一种方式理解观察者模式》

  • 情况二:
    还有假如 北京航站楼 只关注 飞机1 的变化,而不关注 飞机2 的变化,同理, 南京航站楼 只关注 _飞机2_的变化, 而不关注 飞机1 的变化

那这样情况就比较复杂了

学术一点的意思就是:要让多个对象都具有观察者模式(让 多个 观察者 可以关注 多个 自己想关注的 观察对象)。

这时候分析下,相比较之前有什么变化

  • 首先,存放 观察者(航站楼) 的数组 的个数不能固定,且类别/附属 应该 与 观察对象 关联挂钩
  • 再者,观察对象被观察属性 也不应该固定(之前只关注了 飞机1 的 location ,比如,我们可以观察 飞机 的 地理位置,同样也可以关注 飞机 的 海拔高度 等信息)

用图表示 应该就是下面这种 数据情景

《换一种方式理解观察者模式》

所以这里 我们定义的一个通用函数 ,它应该具备 上述的 一些特性

  • 多个观察者
  • 多个观察者对象的多个属性
  • 给 他们添加 订阅,取消订阅,发布的方法

也就是说:每个观察者对象,都独立维护一套独立的观察者模式。

根据需要,我们抽象出下面 这样一个通用的函数:

//通用代码
var observer = {
    //订阅
    addSubscriber: function (callback) {
        this.subscribers[this.subscribers.length] = callback;
    },
    //退订
    removeSubscriber: function (callback) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (this.subscribers[i] === callback) {
                delete (this.subscribers[i]);
            }
        }
    },
    //发布
    publish: function (what) {
        for (var i = 0; i < this.subscribers.length; i++) {
            if (typeof this.subscribers[i] === 'function') {
                this.subscribers[i](what);
            }
        }
    },
    // 通过遍历,给 观察者对象 o 添加 观察者容器,订阅,退订,发布
    make: function (o) { 
        for (var i in this) {
            o[i] = this[i];
            // 每个 观察者对象  都维护自身的一个 观察者 数组
            o.subscribers = [];
        }
    }
};

OK,已经抽象出了 具体的 范式,下面就结合具体情景来看下看是否符合

下面 先分别定义 飞机1,飞机2 这两个观察者对象的 多个属性(地理位置 和 海拔)

var plan1 = {
      location: '北京',
      height: '200km'
}

var plan2 = {
      location: '云南',
      height: '100km'
}

再,分别定义 观察者 的行为(航站楼1,航站楼2 分别关注 飞机的运行状态)

var hangzhanlou1 = function hangzhanlou1(cb) {
      console.log('航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...')
      // 订阅的对象发生了变化 , 观察者 做一些自己想做的事...
      cb();
}

var hangzhanlou2 = function hangzhanlou2 (cb) {
      console.log('航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...')
      // 订阅的对象发生了变化 , 观察者 做一些自己想做的事...
     cb();
}

这里,航站楼1 可以 订阅 飞机1,也可以订阅 飞机2 ,也可以都订阅

同样,航站楼1 可以订阅 飞机的 位置,也可以订阅飞机的 海拔, 也可以都订阅

  • 现实层面上:飞机运行,发生状态的变化
  • Web层面上:一系列事件操作导致的状态变化

给 飞机1,飞机2的 地理位置,海拔高度 都 添加 观察者模式

observer.make(plan1);
observer.make(plan2);

《换一种方式理解观察者模式》

《换一种方式理解观察者模式》

  • 情景1:航站楼1 关注 飞机1
    《换一种方式理解观察者模式》

添加 观察者 hangzhanlou1

plan1.addSubscriber(hangzhanlou1)

飞机1 经过飞行,判断 关注的信息是否发生了变化

这里判断是伪代码,但是这个判断的逻辑非常!非常!非常!的重要!如果有时间,这个后续会展开讲解,因为这里是另外一个核心内容,diff变化,从而控制View层的渲染!

 // 这里为了演示方便,默认判断 地理,海拔都变化了(其实也可以判断 观察者某一个 属性)
function checkChange(val, oldVal) {
    if (val !== oldVal) {
         plan1.publish(function(){
                console.log('飞机1 现在位置是上海,海拔是100km');
          })
    }
}

// 航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是上海,海拔是100km
  • 情景2:航站楼2 关注 飞机1
    《换一种方式理解观察者模式》
    添加 观察者 hangzhanlou2
plan1.addSubscriber(hangzhanlou2)

同样判断状态的改变,进行发布

function checkChange(val, oldVal) {
    if (val !== oldVal) {
         plan1.publish(function(){
                console.log('飞机1 现在位置是太原,海拔是120km');
          })
    }
}

// 航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是太原,海拔是120km
// 航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是太原,海拔是120km
  • 情景3:航站楼1 关注 飞机2
    《换一种方式理解观察者模式》

添加 观察者 hangzhanlou1

plan2.addSubscriber(hangzhanlou1)

同样判断状态的改变,进行发布

function checkChange1(val, oldVal) {
    if (val !== oldVal) {
         plan1.publish(function(){
                console.log('飞机1 现在位置是青海,海拔是300km');
          })
    }
}

// 航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是青海,海拔是300km
// 航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是青海,海拔是300km

function checkChange2(val, oldVal) {
    if (val !== oldVal) {
         plan2.publish(function(){
                console.log('飞机2 现在位置是西藏,海拔是500km');
          })
    }
}

// 航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机2 现在位置是西藏,海拔是500km
  • 情景4:航站楼2 关注 飞机2
    《换一种方式理解观察者模式》

添加 观察者 hangzhanlou2

plan2.addSubscriber(hangzhanlou2)

同样判断状态的改变,进行发布

function checkChange1(val, oldVal) {
    if (val !== oldVal) {
         plan1.publish(function(){
                console.log('飞机1 现在位置是青海,海拔是300km');
          })
    }
}

// 航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是青海,海拔是300km
// 航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机1 现在位置是青海,海拔是300km

function checkChange2(val, oldVal) {
    if (val !== oldVal) {
         plan2.publish(function(){
                console.log('飞机2 现在位置是西藏,海拔是500km');
          })
    }
}

// 航站楼1 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机2 现在位置是西藏,海拔是500km
// 航站楼2 收到了 我关注的 飞机 的运行状态发生了改变...
// 飞机2 现在位置是西藏,海拔是500km
  • 情景5:航站楼2 取消订阅 飞机1(因为飞机1已经 安全降落了)

《换一种方式理解观察者模式》

在 飞机1 上移除 观察者 hangzhanlou2

plan1.removeSubscriber(hangzhanlou2)

最后

观察者模式大体就是如此,但是在开发中为了适应各种场景可能会有很多变种,但是万变不离其中。

上面的代码只是用来帮助理解的,针对 航班 与 航站楼 这种情景 还有很多改进的地方。

以上只是个人理解的一些拙见,如有不对之处还请海涵,并希望大家帮忙纠正!

拓展阅读

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5a3dc5bd6fb9a0451239262e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞