本文已参加【新人创作礼】活动,一起开启掘金创作之路。
背景
观察者模式是Vue2响应式原理的基本设计模式,本文争取用较少的文字和代码,讲明白啥是观察者模式。
介绍
举一个例子来引出概念:
有一商品,价格随时都变化;
有俩消费者,希望该商品价格一旦变动,能及时知道这个变化,并将最新的价格信息更新记录到自己的小本本上。
一个最直观的实现方式:
- 这俩消费者定时的去该商品的官网上看下价格,如果价格变化了,就更新记录到小本本。
但上面的方式笨拙且不现实。一种更为智能的方式是:
- 这俩消费者订阅这个商品的微信公众号(假设有)。商品价格一旦有变化,公众号能够及时发布给这俩人价格变化的消息,俩人再更新到小本本上。
上面的流程中提到了五个概念,分别是:
- 两个名词:
商品、消费者 - 三个动词:
订阅、发布、更新
我们用上面五个概念来抽象描述下观察者模式概念:
消费者订阅商品信息,商品信息发生变动,能够及时发布给消费者,消费者更新记录。
如果能用代码实现上面五个概念以及相关行为,那么我们其实就实现了观察者模式。
商品 & 消费者
商品和消费者是一个名词,在面向对象的编程范式中可以将名词用类来定义:
class Product{
constructor(val) {
this.price = val
}
}
class Consumer {
constructor(name) {
this.name = name
}
}
const product = new Product(100) // 一个商品,初始价格100元
const con1 = new Consumer('张三') // 消费者1
const con2 = new Consumer('李四') // 消费者2
- 定义一个
Product类来描述商品,它有一个属性price来表示商品价格。 - 定义一个
Consumer类来描述消费者,有一个属性name来表示消费者姓名。
订阅
订阅是一个动词,在代码实现上,可以用一个方法来表示动作。这样执行订阅动作等价为调用订阅方法。
一个问题是:订阅方法是该定义在Product类里,还是Consumer类里呢?
直观的理解是定义在消费者Consumer类上,毕竟:
是
消费者订阅了商品。 用代码说就是:
con1.subscribe(product) // 消费者con1 订阅 商品了product
但这种方式存在的问题是:
con1通过subscribe订阅了product的信息,那么con1知道了product的存在,但product却不知道con1的存在。如果product的price属性变化了,因为product不知道con1的存在,无法通知给con1相关信息。
因此,如果想让product知道con1的存在,订阅方法就需要定义在Product类上,而不是Consumer类上。
class Product {
constructor(val) {
this.price = val
this.conList = []
}
subscribe(con) {
this.conList.push(con)
}
}
product.subscribe(con1) //订阅方法的调用
订阅的信息需要保存到conList里,是因为后续发布的时候需要知道这个关系的存在,才知道发布给谁;同时这里conList是数组而不是用变量来保存subscribe的输入参数,是因为订阅商品的消费者可能不止是一个(比如张三、李四、...)。
发布 & 更新
发布是一个动词,因此可以用一个方法publish里表示。因为是商品来发布信息,所以发布的主体是商品,因此很自然的将publish这个方法定义在Product类。
更新是一个动词,因此也可以一个方法update里表示。因为是消费者来更新信息到小本本上,因此更新的主体是消费者,因此很自然将update这个方法定义在Consumer类。
// 商品
class Product {
constructor(val) {
this.price = val
this.conList = [] // 订阅的消费者列表
}
// 订阅
subscribe(con) {
this.conList.push(con)
}
// 发布
publish(newVal) {
this.price = newVal
this.conList.forEach(item => {
item.update(this)
})
}
}
// 消费者
class Consumer {
constructor(name) {
this.name = name
}
// 更新
update(product) {
console.log(this.name, product.price)
}
}
- 发布的时候,商品要通知到每一个已订阅的消费者,因此是循环遍历conList数组。
// 以下是例子
const product = new Product(100) // 一个商品,初始价格100元
const con1 = new Consumer('张三') // 消费者1
const con2 = new Consumer('李四') // 消费者2
product.subscribe(con1) // 消费者con1订阅了商品product
product.subscribe(con2) // 消费者con1订阅了商品product
product.publish(120) // 商品价格变为120元
// console.log 张三,120
// console.log 李四,120
总结
以上即为观察者模式的一个简单实现。进一步的,在正统的概念里,上文中的商品应该叫目标对象,消费者叫观察者。如果我们站在目标对象的角度来看待这个观察者模式,我们可以做如下总结:
目标对象中有一个数组属性,保存所有的订阅的观察者;目标对象有一个订阅方法,用于将观察者记录到数组中;目标对象有一个发布方法,用于遍历数组,调用每个观察者的更新方法;