Observer模式

1、解决的问题

Javascript在进行应用开发时是基于事件驱动的,在处理稍微复杂一点的逻辑时,就有可能出现 在某个事件的完成阶段要处理若干个事件回调的情况。

可能会出现这样的代码:

btn.onclick = function() {
    sideBar.style.backgroundColor = "red";
    sideBar.name = "new_sideBar";

    foot.style.backgroundColor = "red";
    foot.name = "new_name";

    // ....
}

或者这样

btn.onclick = function() {
    dealSideBar();
    dealFoot();
    // ...
}

function dealSideBar() {};
function dealFoot() {};
// ...

以上两种写法相同,都是把要做的事情交托给事件侦听器来做,在处理复杂逻辑时,上述的dealXXX()可能就会达到7个8个,甚至更多,代码都写在一个javascript文件中,显得冗长,因此,上述写法适合相对简单的应用。

当应用变复杂,或者我们需要在事件到达某种状态时按消息类型通知不同的子组件,子系统时,就可以使用Observer模式,Observer模式就是各个子组件要做的事情交还给子组件本身,仅提供订阅消息(事件状态变化),与取消订阅的服务。

在这里插入图片描述
不太理解Observerable的这种命名方式,我用Publisher来代替把。

    class Publisher {
        constructor(obj) {
            this.obj = obj;
            this._listeners = new Map();
        }

        add(e, fn) {
            if (!this._listeners.has(e)) this._listeners.set(e, [fn]);
            else this._listeners.get(e).push(fn);
        }

        notify(e) {
            const fns = this._listeners.get(e.type);
            if (fns){
                for (let fn of fns) {
                    fn();
                }
            } 
        }
    }

// 子组件自由决定 是否订阅,以及消息通知到达后要做的动作

测试样例

    const btn = document.getElementById("btn");
    const pub = new Publisher(btn);
    btn.onclick = e => pub.notify(e);

    // 子组件sidebar
    const sideBar = document.getElementById("sideBar");
    pub.add("click", function(){
        sideBar.style.background = "red";
    });

    // 子组件foot
    const foot = document.getElementById("foot");
    pub.add("click", function(){
        foot.style.background = "orange";
    })

在这里插入图片描述

Observer模式将操作的责任分配给了组件本身(订阅者),而发布者只负责管理订阅列表与通知订阅列表。

Observer有两种应用:基于事件模型、基于我们自己写的对象

以下分开讨论

2、基于事件模型应用

(1)Dom事件
// 事件监听器是发布者,body是订阅者
document.body.onclick = function() {...}
// 事件状态改变,命中订阅,执行回调
document.body.click();
(2)自定义事件

例子源自《JavaScript设计模式与开发实践》一书,大致场景为顾客要买房,但心怡面积和价格的房子目前售罄,需要等销售后续通知再进行购买。

以下为我在原书的基础上使用es6 class重写了一遍代码:

// 售楼处作为发布者
class SalesOffice {
    constructor() {
        this.clients = [];
    }

    listen(fn) {
        this.clients.push(fn);
    }

    trigger() {
        for(let fn of this.clients) {
            fn.apply(this, arguments);
        }
    }
}

const office = new SalesOffice();

// 小明订阅
office.listen((price, area) => console.log(`我是小明,告诉我:价格为${price},面积为${area}`));
// 小红订阅
office.listen((price, area) => console.log(`我是小红,告诉我:价格为${price},面积为${area}`));

office.trigger(10000, 180);
office.trigger(15000, 190);

上面的实现存在缺陷:无论订阅者是否感兴趣,只要数据发生变化,都会通知给所有的订阅者。所以输出会是:

我是小明,告诉我:价格为10000,面积为180
我是小红,告诉我:价格为10000,面积为180
我是小明,告诉我:价格为15000,面积为190
我是小红,告诉我:价格为15000,面积为190

为了避免这种“垃圾短信”,需要给每个顾客打上一个interest的标签,以实现顾客只收到感兴趣的内容:

// 上面的实现存在缺陷:无论订阅者是否感兴趣,只要数据发生变化,都会通知给所有的订阅者。

// 订阅者可只收到感兴趣的通知
class SalesOffice2 {
    constructor() {
        this.clients = new Map();
    }

    listen(interest, fn) {
        if(this.clients.has(interest)) this.clients.get(interest).push(fn);
        else this.clients.set(interest, [fn]);
    }

    trigger() {
        let interest = Array.prototype.shift.apply(arguments);
        let arr = this.clients.get(interest);
        if(arr) {
            for(let fn of arr) {
                fn.apply(this, arguments);
            }
        }
        else{
            console.warn(`Nobody is interested in this topic!`);
        }
    }
}

const office2 = new SalesOffice2();
office2.listen("area150", (price, area) => console.log(`我是小明,关心150平方大小的房子,告诉我:价格为${price},面积为${area}`));
office2.listen("area180", (price, area) => console.log(`我是小红,关心180平方大小的房子,告诉我:价格为${price},面积为${area}`));

// 大小为150的房子价格更新了
office2.trigger("area150", 300000, 150);
// 大小为180的房子价格更新了
office2.trigger("area180", 320000, 180);

这样,小明只收到了area为150的价格变动通知,小红只收到area为180的价格变动通知

我是小明,关心150平方大小的房子,告诉我:价格为300000,面积为150
我是小红,关心180平方大小的房子,告诉我:价格为320000,面积为180

3、基于对象应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值