关于观察者模式/发布订阅模式我所知道的

简介: 关于观察者模式/发布订阅模式我所知道的

image.png


本文已参与「新人创作礼」活动,一起开启掘金创作之路。

关键词:Observable-Observer EventEmitter-EventListener


什么是观察者模式


定义:在对象之间定义一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

类比到生活中,就像是为了逃避城管的小摊贩,每天出摊的时候就在朋友圈或者微信群里发一句:今天在 xxx 出摊。

我们先来看一个观察者模式的简单实现:

// 摊主
const shop = {
  // 微信群友
  observers: [],
  // 进微信群
  subscribe: function (fn) {
    this.observers.push(fn)
  },
  // 发布消息
  notify: function () {
    this.observers.forEach(fn => fn.apply(this, arguments))
  }
};
// 小k 进群
shop.subscribe((address) => {
  console.log(`小k: 今天在${address}出摊。`);
});
// 大D 进群
shop.subscribe((address) => {
  console.log(`大D: 今天在${address}出摊。`);
});
shop.notify('人民广场');
shop.notify('提篮桥');
// 小k: 今天在人民广场出摊。
// 大D: 今天在人民广场出摊。
// 小k: 今天在提篮桥出摊。
// 大D: 今天在提篮桥出摊。

将上面这段代码梳理下业务逻辑,如图示:


image.png


在观察者模式中有两个主要角色:Observable(观察对象)和 Observer(观察者)。

  1. 观察者监听观察对象
  2. 观察对象的状态发生变化时就会通知所有的观察者
  3. 观察者更新自己

整个过程里,观察对象负责监视事件,观察者负责处理收到的数据。

  • Observable 观察对象
  • observers  观察者列表
  • subscribe  注册
  • unsubscribe 取消注册
  • notify     通知观察者
  • Observer 观察者
  • update 更新自己

用 ts 表达会更直观一些。

interface IObservable {
  subscribe(observer: Observer): void;
  unsubscribe(observer: Observer): void;
  notify: Function;
}
interface IObserver {
  update: Function;
}
class Observable implements IObservable {
  private observers: IObserver[] = [];
  // 注册
  public subscribe(observer: IObserver): void {
    this.observers.push(observer);
  }
  // 取消注册
  public unsubscribe(observer: IObserver): void {
    const n: number = this.observers.indexOf(observer);
    n != -1 && this.observers.splice(n, 1);
  }
  // 通知
  public notify(...args): void {
    this.observers.forEach((observer) => observer.update(...args));
  }
}
class Observer implements IObserver {
  constructor(private name: string) {}
  // 更新
  update(address) {
    console.log(`${this.name}:今天在${address}出摊`);
  }
}
const subject: Observable = new Observable();
const k = new Observer("小K");
const D = new Observer("大D");
subject.subscribe(k);
subject.subscribe(D);
subject.notify("人民广场");
// 小K:今天在人民广场出摊
// 大D:今天在人民广场出摊
subject.unsubscribe(k);
subject.notify("提篮桥");
// 小K:今天在提篮桥出摊


什么是发布订阅模式?


相比于简单的观察者模式,发布订阅多了一个映射(topics)。-> 关联一下 策略模式。

先来看张图:


image.png


从图中我们可以看到,相比于简单的观察者模式,发布订阅多了 topics 映射,所有发布者的消息都需要通过 topics 派发(dispatch) 到订阅者。

为什么这么设计呢?

如果 Observable 过于复杂,notify 通知所有 observer 就会导致性能问题。

换句话说:微信群里这么多人,阿猫阿狗都在水群,但是我只想知道今天的出摊信息,那怎么办?

我会把整个群聊屏蔽了,除了几个特别关注特别是:摊主、网红。

// 小摊群
const shop = {
  // 粉丝团
  observers: {},
  // 进粉丝团
  subscribe: function (key, fn) {
    // 如果还没有订阅过此类消息,给该类消息创建一个缓存列表
    if (!this.observers[key]) {
      this.observers[key] = [];
    }
    // 订阅的消息添加进消息缓存列表
    this.observers[key].push(fn);
  },
  // 发布消息
  notify: function () {
    // 取出消息类型
    const key = Array.prototype.shift.call(arguments)
    // 取出该消息对应的回调函数集合
    const fns = this.observers[key];
    // 如果没有订阅该消息,则返回
    if (!fns || fns.length === 0) {
      return false;
    }
    fns.forEach(fn => fn.apply(this, arguments));
  },
  unsubscribe: function (key, fn) {
    let fns = this.observers[key];
    // 如果key对应的消息没有被人订阅,则直接返回
    if (!fns) {
      return false;
    }
    // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
    if (!fn) {
      this.observers[key] = null
    } else {
      this.observers[key] = fns.filter(_fn => _fn !== fn);
    }
  }
};

测试代码:

// 添加 摊主 - k 订阅
shop.subscribe('master', k = function (address) {
  console.log(`小k: 今天在${address}出摊。`);
});
// 添加 摊主 - D 订阅
shop.subscribe('master', D = function (address) {
  console.log(`大D: 今天在${address}出摊。`);
});
shop.notify('master', '人民广场');
// 添加 网红 - k 订阅
shop.subscribe('网红', k = function (address) {
  console.log(`小k: 今天在${address}出摊。`);
});
// 添加 网红 - D 订阅
shop.subscribe('网红', D = function (address) {
  console.log(`大D: 今天在${address}出摊。`);
});
// 删除订阅
shop.unsubscribe('网红', D);
shop.notify('网红', '提篮桥');
// 小k: 今天在人民广场出摊。
// 大D: 今天在人民广场出摊。
// 小k: 今天在提篮桥出摊。


这样我们就不用把群里的每一条消息都看过去了。


应用场景


什么时候使用?当一个对象的改变需要同时改变其他对象的时候。

满足以下两个条件:

  • 一对多。当一个对象的改变需要同时改变其他对象。
  • 不知道多几个。不知道具体有多少对象需要改变。

典型案例:网站登录。

需求: 用户登录之后更新头像 avatar , 更新消息列表 message , 刷新购物车 cart 。

login.succ(function (data) {
  // 更新头像
  header.setAvatar(data.avatar);
  // 刷新消息列表
  message.refresh();   
  // 刷新购物车列表             
  cart.refresh();                   
});

需求蔓延:新增地址簿 address 模块,登陆之后也刷新下。

login.succ(function (data) {
  // 新增代码
  address.refresh();                   
});

需求蔓延:那个新增 收藏夹 favorites 也更新下?新增 订单列表 orders 也更新下?

就这样,后续面对的是越来越多这样突如其来的业务诉求,面对这种情况,就可以用上发布订阅了。

// 登录成功
login.succ(function (data) {
  // 发布登录成功的消息   
  login.trigger('loginSucc', data);    
});
// 各个业务模块注册登录成功事件
login.listen('loginSucc', function (data) {
  header.setAvatar(data.avatar);
});
login.listen('loginSucc', function (data) {
  message.refresh( data );
});
login.listen( 'loginSucc', function( obj ){
  address.refresh( obj );
});


参考资料:

细数九种常见的设计模式

观察者模式

目录
相关文章
|
2月前
|
消息中间件 存储 Cloud Native
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
|
2月前
|
消息中间件 缓存 监控
【C++ 观察者模式的应用】跨进程观察者模式实战:结合ZeroMQ和传统方法
【C++ 观察者模式的应用】跨进程观察者模式实战:结合ZeroMQ和传统方法
139 1
|
2月前
|
设计模式
设计模式-观察者(发布订阅)模式
设计模式-观察者(发布订阅)模式
|
2月前
|
消息中间件 设计模式 前端开发
【面试题】说说你对发布订阅、观察者模式的理解?区别?
【面试题】说说你对发布订阅、观察者模式的理解?区别?
|
2月前
行为型 观察者模式(发布订阅)
行为型 观察者模式(发布订阅)
24 0
|
9月前
4 # 发布订阅模式
4 # 发布订阅模式
30 0
|
消息中间件 设计模式 Java
SpringBoot事件监听机制及观察者模式/发布订阅模式
SpringBoot事件监听机制及观察者模式/发布订阅模式
300 0
|
设计模式 开发者
设计模式之订阅发布模式
设计模式之订阅发布模式
173 0
|
设计模式 资源调度 Dart
发布订阅模式原理及其应用
本文介绍发布订阅模式原理及其应用
135 0
|
设计模式 JavaScript 调度
它们不一样!透析【观察者模式】和【发布订阅模式】
观察者模式常常会和发布订阅模式一起哪来比较,它们二者同样重要。