观察者模式

33 阅读3分钟

一、观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 —— Graphic Design Patterns

单看看上面的定义比较不好理解,观察者就是由一个发布者和一个订阅者或多个订阅者组成,比如在一个群聊群主可以@任何成员并发布消息,群主就是一个发布者,其他的成员接收到群主的消息并执行任务就是订阅者。

需要注意的是观察者模式发布-订阅模式有一定的区别,具体的区别后面会讲!

1.实现一个观察者模式

首先创建一个发布者,这个发布者具有以下功能 可以添加订阅者,移出订阅者,通知订阅者。可以理解为群主是发布者可以邀请新成员、踢出成员、并且@通知所有成员

 class Publisher {
    constructor() {
        this.observers = []
        console.log('Publisher created')
    }
    // 增加订阅者
    add(observer) {
        console.log('Publisher.add invoked')
        this.observers.push(observer)
    }
    // 移除订阅者
    remove(observer) {
        console.log('Publisher.remove invoked')
        this.observers.forEach((item, i) => {
            if (item === observer) {
                this.observers.splice(i, 1)
            }
        })
    }
    // 通知所有订阅者
    notify() {
        console.log('Publisher.notify invoked')
        this.observers.forEach((observer) => {
            observer.update(this)
        })
    }
}

订阅者就是去执行发布者的任务

 class Observer {
    constructor() {
        console.log('Observer created');
    }

    update() {
        console.log('执行任务');
    }
}

测试 创建订阅者和创建发布者,并添加订阅者,和移出订阅者

const liLei = new Observer()
const limeishan = new Publisher()    
limeishan.add(liLei)
limeishan.remove(liLei)

然后我们可以新增一个setState方法用来更新文档,当文档更新后就直接通知所有的订阅者接收新的文档并开始工作

  class Publisher {
    constructor() {
        this.observers = []
        console.log('Publisher created')
    }
    // 增加订阅者
    add(observer) {
        console.log('Publisher.add invoked')
        this.observers.push(observer)
    }
    // 移除订阅者
    remove(observer) {
        console.log('Publisher.remove invoked')
        this.observers.forEach((item, i) => {
            if (item === observer) {
                this.observers.splice(i, 1)
            }
        })
    }
    // 通知所有订阅者
    notify() {
        console.log('Publisher.notify invoked')
        this.observers.forEach((observer) => {
            observer.update(this)
        })
    }
}
//创建订阅者
//订阅者就是去执行发布者的方法任务
class Observer {
    constructor() {
        console.log('Observer created');
    }

    update() {
        console.log('执行任务');
    }
}
// 定义一个具体的需求文档(prd)发布类
class PrdPublisher extends Publisher {
    constructor() {
        super()
        // 初始化需求文档
        this.prdState = null
        // 韩梅梅还没有拉群,开发群目前为空
        this.observers = []
        console.log('PrdPublisher created')
    }

    // 该方法用于获取当前的prdState
    getState() {
        console.log('PrdPublisher.getState invoked')
        return this.prdState
    }

    // 该方法用于改变prdState的值
    setState(state) {
        console.log('PrdPublisher.setState invoked')
        // prd的值发生改变
        this.prdState = state
        // 需求文档变更,立刻通知所有开发者
        this.notify()
    }
}
class DeveloperObserver extends Observer {
    constructor() {
        super()
        // 需求文档一开始还不存在,prd初始为空对象
        this.prdState = {}
        console.log('DeveloperObserver created')
    }

    // 重写一个具体的update方法
    update(publisher) {
        console.log('DeveloperObserver.update invoked')
        // 更新需求文档
        this.prdState = publisher.getState()
        // 调用工作函数
        this.work()
    }

    // work方法,一个专门搬砖的方法
    work() {
        // 获取需求文档
        const prd = this.prdState
        // 开始基于需求文档提供的信息搬砖。。。
        console.log('996 begins...')
    }
}

2.完成vue2响应式原理

具体可以参考这篇文章juejin.cn/book/684473…

Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Watcher 对象进行视图更新。

    
function observer(target) {
    if (target && typeof target === 'object') {
        Object.keys(target).forEach((key) => {
            defineReactive(target, key, target[key])
        })
    }
}
function defineReactive(target, key, val) {
    //属性值可能是object  需要进行递归
    const dep = new Dep()
    observer(val)
    Object.defineProperty(target, key, {
        enumerable: true,
        configurable: false,
        get: function () {
            return val
        },

        set: function (value) {
            dep.notify()
        }
    })
}
//实现订阅者
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(subs) {
        this.subs.push(subs)
    }

    notify() {
        this.subs.forEach((item) => {
            item.update()
        })
    }
}

3.实现发布-订阅模式

实现一个全局事件总线

 class EventEmitter {
    constructor() {
        // handlers是一个map,用于存储事件与回调之间的对应关系
        this.handlers = {}
    }

    //订阅事件
    on(eventName, callback) {
        if (!this.handlers[eventName]) {
            this.handlers[eventName] = []
        }
        this.handlers[eventName].push(callback)
        console.log(this.handlers);
    }


    //触发事件
    emit(eventName) {
        if (this.handlers[eventName]) {
            const handlers = this.handlers[eventName]

            handlers.forEach(element => {
                element()
            });
        }
    }

    //取消订阅
    off(eventName, cb) {
        const callbacks = this.handlers[eventName]
        let index = callbacks && callbacks.indexOf(cb)
        if (index != -1) {
            callbacks.splice(index, 1)
        }
    }
    //仅仅订阅一次

    once(eventName, cb) {
        if (this.handlers[eventName] && this.handlers[eventName].indexOf(cb)) {
            return
        }
        this.on(eventName, cb)
    }

}


const emitter = new EventEmitter()

emitter.on('change', () => {
    console.log(123)
})



emitter.emit('change')
观察者模式和发布-订阅模式区别

发布者直接触及到订阅者的操作,叫观察者模式。
由发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布-订阅模式。