本文已参加【新人创作礼】活动,一起开启掘金创作之路。
背景
观察者模式
是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
总结
以上即为观察者模式的一个简单实现。进一步的,在正统的概念里,上文中的商品
应该叫目标对象
,消费者
叫观察者
。如果我们站在目标对象
的角度来看待这个观察者模式,我们可以做如下总结:
目标对象
中有一个数组属性,保存所有的订阅的观察者
;目标对象
有一个订阅
方法,用于将观察者记录到数组中;目标对象
有一个发布
方法,用于遍历数组,调用每个观察者的更新
方法;