0%

javascript-observer-mode

Introduction

观察者模式是设计模式中非常重要的一个模式,它属于行为模式的一种。下面是wikipedia中对观察者模式的定义。

In software design and engineering, the observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

是不是看得一头雾水?我们先以一个现实中的例子来解释观察者模式。假设你现在正在看一场球赛,那么这时候你是观察者(Observer),而比赛是被观察者(Observable),或者叫主题(Subject)。比赛中如果你支持的球队进球了,你会欢呼跳跃,而如果对方进球,你会沮丧失望。你的状态会根据比赛的状态而改变,这就是观察者模式的一个例子。

观察者模式有以下特征:

  1. Subject和观察者之间是一对多的关系。
  2. 每个观察者有一个update方法。
  3. Subject(被观察者)维护一个观察者列表。
  4. 当Subject状态发生变化时,对于列表中每个观察者,都会调用它们的update方法通知他们。

Define Observer

下面的代码为每个观察者指定一个名字,当Subject发生变化时,Observerupdate方法会被调用,打印出观察者的名字。update方法就是被观察者和观察者联系的纽带。

1
2
3
4
5
6
7
8
9
10
11
export default class Observer {
private readonly name;

constructor(name: string) {
this.name = name;
}

update() {
console.log(`${this.name} updated`);
}
}

Define Subject

Subject中文翻译为主题,它是一个对象,拥有注册观察者、删除观察者、通知观察者的方法。当某个特定事件发生时,Subject会通知所有观察者,让它们做出相应的反应。所以Subject实际上就是被观察者。

下面的代码为Subject添加了一个观察者数组,当Subject发生变化时,会遍历观察者数组,调用每个观察者的update方法。subscribe方法实际上就是addObserver方法,unsubscribe方法实际上就是removeObserver方法。这两个方法用来将观察者加入或者移除观察者数组。被移除数组的观察者无法再收到Subject的通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Observer from './Observer';

export default class Subject {
private observers: Observer[] = [];

subscribe(observer: Observer) {
this.observers.push(observer);
}

unsubscribe(observer: Observer) {
this.observers = this.observers.filter((o) => o !== observer);
}

notify() {
this.observers.forEach((o) => o.update());
}
}

Use Observer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Observer from './Observer';
import Subject from './Subject';

const subject = new Subject();
const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');
const observer3 = new Observer('Observer 3');

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.subscribe(observer3);
subject.notify(); // 1

subject.unsubscribe(observer1);
subject.notify(); // 2

在 // 1 处,输入如下内容。

1
2
3
Observer 1 updated
Observer 2 updated
Observer 3 updated

在 // 2 处,输入如下内容,因为Observer 1被移除了,所以只有Observer 2和Observer 3收到了通知。

1
2
Observer 2 updated
Observer 3 updated

Summary

  1. Subject(被观察者,也叫Observable):拥有注册观察者、删除观察者、通知观察者的方法。
  2. Observer(观察者):拥有update方法,当Subject发生变化时,Observer的update方法会被调用。
  3. 观察者和被观察者之间是松耦合的,被观察者只知道观察者的接口,而不知道观察者的具体实现。

网上常见的EventBus就是基于观察者模式实现的,当一个组件发生变化时,EventBus会通知所有订阅了这个事件的组件。下面是一个简单的EventBus实现。之所以将EventBus挂在到Window对象上,是因为这样可以在全局范围内使用EventBus。比如在不同的模块中,可以通过EventBus来通信。Module Federation中Remote模块和Host模块之间的通信也可以通过EventBus实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// eventBus.js
class EventBus {
constructor() {
this.listeners = {};
}

on(eventName, callback) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push(callback);
}

emit(eventName, data) {
if (this.listeners[eventName]) {
this.listeners[eventName].forEach(callback => callback(data));
}
}
}

const eventBus = new EventBus();
window.eventBus = eventBus;
1
2
// Emit an event from Remote module.
window.eventBus.emit('someEvent', { message: 'Hello from Remote' });
1
2
3
4
// Listen for an event from Host module.
window.eventBus.on('someEvent', (data) => {
console.log(data.message); // 输出: Hello from Remote
});

注意上面这个写法并不支持粘性事件,如果要支持粘性事件,该如何改进呢?