3

最近在研究设计模式,特意把前端最常用的发布订阅模式和观察者模式写出来与大家分享一下。希望能给大家带帮助,如果有不对的地方,也请在留言区指出。


一.观察者模式

1.1定义

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. -- wiki

观察者模式,指的是一个主题对象(subject),维护了一个依赖它的观察者(observers)数组,当subject变化的时候,会通知数组中的观察者自动更新它自己

1.2现实举例

房东A想550w卖房子,投资者B、C、D对A说
‘太贵了,500W再联系我们’,然后把自己的手机号放在A这里
A一段时间后,房子卖不出去,降价到500W,根据手机号通知B、C、D

1.3解决的问题

  • A one-to-many dependency between objects should be defined without making the objects tightly coupled.
  • It should be ensured that when one object changes state an open-ended number of dependent objects are updated automatically.
  • It should be possible that one object can notify an open-ended number of other objects. --wiki

一对多的对象依赖之间的解耦合,当被依赖的对象更新的时候,依赖的多个对象会自动更新,我觉得更重要的可能是多个对象是不确定的,未来可以无限添加

怎么理解解耦,观察者模式是一种松耦合关系,observer只需要知道subject的名字,然后有个update方法就行了,这个的observer你可以随便添加,不需要改变subjuct的代码

如果按一般的业务逻辑写法,每添加一个依赖者,我们是需要改被依赖者的代码的,这就是重耦合了。

// 主题色改变
obj.change(()=>{ //回调函数
    obj1.update() // 头部颜色改变
    obj2.update() // 尾部颜色改变
    obj3.update() // 内容颜色改变
    .......  // 一年后新加入的侧边栏模块,颜色也需要该拜年
})

1.4描述

The sole responsibility of a subject is to maintain a list of observers and to notify them of state changes by calling theirupdate()operation.
The responsibility of observers is to register (and unregister) themselves on a subject (to get notified of state changes) and to update their state (synchronize their state with subject's state) when they are notified.
This makes subject and observers loosely coupled. Subject and observers have no explicit knowledge of each other. Observers can be added and removed independently at run-time.
This notification-registration interaction is also known as[publish-subscribe] --wiki

主题(subject)有一个observers数组,观察者(observer)在注册(register)以后会被加入到这个数组重,当subject的state改变以后,会通知(notify)observers数组里面的所有观察者更新(update)

注意最后一句话,观察者模式的这种'注册-通知'关系也叫做发布订阅模式,所以它是发布订阅模式的一种特殊实现

1.5实现

UML@2x.png

我们根据上面的UML图可以用JS简单实现观察者模式
来解决我们上面提到的现实中的例子

class Observer {
    constructor(name){
        this.name = name
    }
    update (price) {
        console.log(`${this.name},你好。我是房东A,我的房子刚刚降价到${price}万,赶紧来买!`)
    }
}
class Subject {
    constructor(price){
        this.price = price
        this.observerList = []
    }
    registerObserver(observer){
        this.observerList.push(observer)
    }
    notifyObserver(){
        this.observerList.forEach(observer=>observer.update(this.price))
    }
    setState (price){ //用来触发notifyObserver
        this.price = price
        if(price<=500){
            this.notifyObserver()
        }
    }
}
const A = new Subject(550)
const B = new Observer('B')
const C = new Observer('C')
const D = new Observer('D')
A.registerObserver(B)
A.registerObserver(C)

setTimeout(function () {
    A.setState(510)
    // 1个月后,D也告诉房东500w一下他也想买
    // 只要D也有update方法,我们直接注册,并不用改变以后的逻辑代码
    // 实现了解耦,维护性好
    A.registerObserver(D)
},1000)
setTimeout(function () {
    A.setState(490)
},3000)

1.6应用

vue响应式实现使用了观察者模式,我们重点关注Dep和watcher
Observer.png

Dep,全名 Dependency,从名字我们也能大概看出 Dep 类是用来做依赖收集的,它做的事情很重要

  1. 定义subs数组,用来收集观察者Watcher
  2. 当劫持到数据变更的时候,通知观察者Watcher进行update操作

Watcher 意为观察者,它负责做的事情就是订阅 Dep ,当Dep 发出消息传递(notify)的时候,所以订阅着 Dep 的 Watchers 会进行自己的 update 操作

为什么用观察者模式?个人认为解耦,可以无限添加扩展watcher

二.发布订阅模式

2.1定义

在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

2.2现实举例

房东A想550w卖房子,和中介说过了,投资者B、C、D对中介
‘有500w以下的房子联系我们’,然后把自己的手机号放在中介这里
A一段时间后,房子卖不出去,降价到500W,中介根据手机号通知B、C、D

2.3解决的问题

模块间的通讯问题,发布者和订阅者两个模块完全不用知道对方的存在,只要有中介就行,

2.4实现

WX20191123-095214@2x.png

我们改一改1.5的例子,实际上只需要一个中介类event就行
来解决我们上面提到的现实中的例子

class Event {
    constructor(){
        this.list = {}
    }
    on(name,fn){
        if(!this.list[name]){
            this.list[name] = []
        }
       this.list[name].push(fn)
    }
    emit(name,data) {
        this.list[name] && this.list[name].forEach(fn=>fn(data))
    }
}

const event = new Event()
event.on('卖房',function (price) {
    if(price>=500){
        console.log(`现在房价${price}万。太高了,再等等吧`)
    }else{
        console.log(`现在房价${price}万。赶紧通知BCD买房`)
    }
})
event.emit('卖房',550)
setTimeout(()=>{event.emit('卖房',520)},1000)
setTimeout(()=>{event.emit('卖房',450)},2000)

2.5应用

发布订阅模式中的发布者和订阅者感觉很模糊,隐藏在中间商后面,看起来发布订阅都是中间商一个人做的

我们只需要搞清楚一点,只要有注册-发布机制的就是发布订阅模式,这是一种解决异步操作的常见方法。只要能在相应的时候完成相应的操作就行了。

  • dom绑定
<button onclick="()=>{document.getElementById("demo").innerHTML = "Hello World";}">Click me</button>

注册click事件,发布(触发)click事件,执行相应操作

  • node events模块
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
myEmitter.on('event1', () => {
  console.log('触发事件');
});
myEmitter.emit('event1');

注册event1事件,发布event1事件,执行相应操作

三.区别

3.1耦合度

观察者模式松耦合,observer需要有update方法,需要被subscribe到subject上
发布订阅模式发布订阅都是由中介代完成的,所以发布者和订阅者无耦合

3.2形态上

观察者模式是一个典型一对多模式,至少有两个类(对象),
发布订阅模式有一个中介商,发布者和订阅者都隐藏在后面,发布订阅动作有中介完成,所以有且只有一个中介对象

3.3从属上

wiki上说的有注册-通知机制的都是发布订阅模式,所以观察者模式是一种特殊的发布订阅模式


题外话,写文章真的很辛苦,写了这么久了,竟然一个打赏也没收到,已哭晕。有没有哪位大哥能给个打赏,让我开开荤,支持支持我呗......

四.参考文献

  1. https://en.wikipedia.org/wiki/Observer\_pattern#What\_problems\_can\_the\_Observer\_design\_pattern\_solve?
  2. https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe\_pattern
  3. https://juejin.im/post/5a6dae12f265da3e4a6fb8e7
  4. https://cn.vuejs.org/v2/guide/reactivity.html
  5. https://zhuanlan.zhihu.com/p/51357583
  6. headFist设计模式
  7. javascript设计模式与开发实践

Runningfyy
1.3k 声望661 粉丝