大白话讲解观察者模式

563 阅读4分钟

本文已参加【新人创作礼】活动,一起开启掘金创作之路。

背景

观察者模式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的存在。如果productprice属性变化了,因为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

总结

以上即为观察者模式的一个简单实现。进一步的,在正统的概念里,上文中的商品应该叫目标对象消费者观察者。如果我们站在目标对象的角度来看待这个观察者模式,我们可以做如下总结:

  1. 目标对象中有一个数组属性,保存所有的订阅的观察者
  2. 目标对象有一个订阅方法,用于将观察者记录到数组中;
  3. 目标对象有一个发布方法,用于遍历数组,调用每个观察者的更新方法;