New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zhaoqize opened this Issue Nov 25, 2017 · 1 comment

Comments

Projects
None yet
2 participants
@zhaoqize
Owner

zhaoqize commented Nov 25, 2017

观察者模式

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

使用观察者模式的好处:

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

理解的第一阶段

我们先用一个通俗的情景来比喻一下,就拿 航班 与 航站楼 之间的关系:
image

图上显示的是 飞机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 的位置了,是在: 南京,我这就准备接机

理解的第二阶段

image

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

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

那这样情况就比较复杂了

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

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

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

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

image

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

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

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

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

//通用代码
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);

image

image

  • 情景1:航站楼1 关注 飞机1
    image

添加 观察者 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
    image
    添加 观察者 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
    image

添加 观察者 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
    image

添加 观察者 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已经 安全降落了)

image

在 飞机1 上移除 观察者 hangzhanlou2

plan1.removeSubscriber(hangzhanlou2)

最后

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

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

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

拓展阅读

@zhaoqize zhaoqize changed the title from 通俗易懂的理解观察者模式 to 换一种方式理解观察者模式 Dec 23, 2017

@yeyuguo

This comment has been minimized.

yeyuguo commented Apr 16, 2018

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment