什么是观察者模式

这一节我们来看 观察者模式观察者模式 在面向前端的设计模式中是非常重要的一种设计模式,它在jQuery、Vue、React 包括原生JavaScript 语法中都有大量的应用。首先我们先来看 观察者模式 的定义。

观察者模式(发布订阅模式)就是:使用一个目标对象来管理所有依赖于它的观察者(订阅者)对象(一个或多个),并且在它本身的状态改变时主动向观察者(订阅者)对象发出通知。

我们用大白话来解释一下上面的定义,观察者模式它又被称为发布订阅模式,意思就是,在观察者模式中,它会存在一个发布者,和一个或者多个订阅者,发布者与订阅者是一种一对一或者一对多的关系。当发布者需要去更新状态的时候,它就会通知所有的订阅者,通过订阅者来完成状态的更新。

这就是 观察者模式(发布订阅模式) 的核心逻辑:当需要更新状态时,发布者会主动通知它的订阅者。那么大家可能会觉得,这样的一种模式他有什么作用呢?大家记住上面这句话,我们来看下面的实例。

举例说明

当我们肯德基吃饭的时候,我们去柜台点餐,点完餐之后,我们拿到一个编号,就可以去座位上等着,这时候我们可以看手机,干什么都行,等到点的餐弄好了之后,营业员会叫我们的编号,我们根据手中的编号直接去柜台领餐就可以。

这样的一种方式,其实就是观察者模式(发布订阅模式) ,我们再来回顾一下它的核心逻辑:当需要更新状态时,发布者会主动通知它的订阅者。在我们上面的事例中,更新状态就是我们点的餐被制作完成了,这时候状态发生了变化,然后发布者就是肯德基的营业员,我们就是这个营业员的订阅者,我们手中的编号就是发布者用来确定我们身份的订阅者编号,营业员通知我们订的餐被制作完成,我们根据手中的编号来去取出汉堡。这就是活生生的 观察者模式(发布订阅模式)当需要更新状态时,发布者会主动通知它的订阅者

OK,然后我们接下来就来绘制一下 观察者模式(发布订阅模式)UML类图

绘制UML类图

在这里插入图片描述

我们看上图中绘制的UML类图,在上图中我们通过Observer表示我们的订阅者,即事例中的客户,Subject表示发布者,即事例中的肯德基商家。所有的订阅者都有一个自己的标记code,并且持有发布者的引用,订阅者提供了一个update的方法,当该方法被调用时,表示订阅者状态更新。而发布者则持有了订阅者的集合,即observers,并且发布者有一个自己的名字name,同时它提供了两个方法,当有新的订阅者被添加到observers的时候,会调用addObserver, 而当调用notifyAllObservers的时候,表示发布者通知所有的订阅者状态需要更新了。

oK,前端中的UML类图设计总是会比较简单。然后我们来看一下代码实现。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Subject {
constructor () {
this.observers = [];
this.name = '发布者';
}

addObserver (observer) {
this.observers.push(observer);
}

notifyAllObservers () {
this.observers.forEach(observer => {
observer.update();
});
}
}

class Observer {
constructor (code, subject) {
this.code = code;
this.subject = subject;
this.subject.addObserver(this);
}

update () {
console.log(`${this.subject.name}通知订阅者${this.code}更新`);
}
}

如代码所示,我们首先创建了发布者Subject,并为他创建了两个方法addObserver 和 notifyAllObservers,分别用来添加订阅者和更新订阅者状态。然后创建了订阅者Observer,订阅者中保存一个编号code,同时持有了它的发布者对象subject,并为subject添加了订阅者也就是它自己在this.subject.addObserver(this);,然后创建了一个update方法,表示状态更新。

然后我们就需要分别创建发布者和订阅者,如下面代码所示:

1
2
3
4
5
6
7

const subject = new Subject();
const o1 = new Observer('1', subject);
const o2 = new Observer('2', subject);
const o3 = new Observer('3', subject);

subject.notifyAllObservers();

我们分别创建了subjectobserver,然后通过notifyAllObservers,来通知所有的订阅者更新状态。

最后的打印结果如下:

1
2
3
发布者通知订阅者1更新
发布者通知订阅者2更新
发布者通知订阅者3更新

到这里,大家应该已经对什么是 观察者模式(发布订阅模式) 有了一个完整的认识了吧。然后我们就来看一下 观察者模式(发布订阅模式) 的使用场景。

使用场景

观察者模式在前端中的使用场景非常的多,比如Vue中的响应式数据渲染机制也都是通过 观察者模式(发布订阅模式) 来实现的,这一块内容我在另外一本书 深入浅出学习Vue开发 中做了详细的演示,并且通过 观察者模式(发布订阅模式) 实现了一个响应式的框架。这一块的内容整体比较负责,我们本书主要目的是讲解设计模式,所以就不去采用这么复杂的事例了,如果大家对 响应式数据渲染 这一块内容感兴趣的,可以看一下我的这本书 深入浅出学习Vue开发

我们在这里准备了两个事例,第一个是js中的事件绑定机制。

1
2
3
div.click = function () {
alert('onclick');
}

js中的事件绑定我们基本上天天都在写,但是可能很少有人知道这是使用了 观察者模式(发布订阅模式) 来实现的。那么在这一段代码里面,谁是发布者,谁是订阅者?我们再来回顾一下 观察者模式(发布订阅模式) 的定义:当需要更新状态时,发布者会主动通知它的订阅者 。当我们执行了上面的这段代码之后,alert('onclick'); ,他并不会立刻执行,它什么时候执行取决于我们什么时候去点击这个div,触发divclick事件。

OK,那么我们把它换成 观察者模式(发布订阅模式) 的语法来解释一下就是,订阅者alert('onclick');会在接收到发布者div.click的通知的时候才回去执行。对吧 , 这就是一个标准的 观察者模式(发布订阅模式) 机制。

然后第二个事例,我们来看PromisePromise是我们比较常用的一种异步编程的解决方案,而它的实现也是使用了 观察者模式(发布订阅模式) 。我们一起来看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
function createPromise (src) {
return new Promise ((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => {
resolve(img);
}
img.onerror = () => {
reject(img);
}
img.src = src;
document.body.appendChild(img);
});
}

看上面的代码,我们通过createPromise方法创建并返回了一个Promise对象,我们利用这个Promise创建了一个img标签并为它设置了图片地址,当图片加载成功的时候,回调resolve,当图片加载失败的时候,回调reject

然后我们就可以通过Promise对象的then方法来响应Promise的状态变化

1
2
3
4
5
6
7
8
const src = 'https://images.gitbook.cn/logo.png';

const promise = createPromise(src);
promise.then(() => {
console.log('图片加载完成');
}).catch(() => {
console.log('图片加载失败');
});

总结

我们本章学习的是 观察者模式观察者模式又被成为发布订阅模式 , 原因就是因为在 观察者模式 中总会使用一个目标对象(发布者)来管理所有依赖于它的观察者(订阅者)对象(一个或多个)。

在我们的前端开发中 观察者模式(发布订阅模式) 的使用是非常广泛的,我们上面列出的例子也只是冰山一角。另外如果大家想要学习好设计模式的话,那么一定要记住我们在第一章所说的,设计与模式是分开的两个概念,我们可以把设计当成一种理念,模式则是使理念具现化的一种格式,对于我们的学习一定要,重设计轻模式,掌握理念而不是拘泥于形式。