zhaoqize / blog

✍️qize的博客:原创文章、外文翻译、技术总结和演示代码
https://zhaoqize.github.io/blog/
MIT License
280 stars 74 forks source link

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

Open zhaoqize opened 6 years ago

zhaoqize commented 6 years ago

观察者模式

观察者模式又叫发布订阅模式(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

那这样情况就比较复杂了

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

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

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

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 可以订阅 飞机的 位置,也可以订阅飞机的 海拔, 也可以都订阅

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

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

image

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

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

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

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

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

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

image

在 飞机1 上移除 观察者 hangzhanlou2

plan1.removeSubscriber(hangzhanlou2)

最后

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

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

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

拓展阅读

👆返回主页查看更多文章👆
yeyuguo commented 6 years ago

👍