九、观察者模式(Observer Patterns)

1.概念介绍

观察者模式(Observer Patterns) 也称订阅/发布(subscriber/publisher)模式,这种模式下,一个对象订阅定一个对象的特定活动,并在状态改变后获得通知。
这里的订阅者称为观察者,而被观察者称为发布者,当一个事件发生,发布者会发布通知所有订阅者,并常常以事件对象形式传递消息。

所有浏览器事件(鼠标悬停,按键等事件)都是该模式的例子。

我们还可以这么理解:这就跟我们订阅微信公众号一样,当公众号(发布者)群发一条图文消息给所有粉丝(观察者),然后所有粉丝都会接收到这篇图文消息(事件),这篇图文消息的内容是发布者自定义的(自定义事件),粉丝阅读后可能就会买买买(执行事件)。

2.观察者模式 VS 发布订阅模式

2.1观察者模式

一种一对多的依赖关系,多个观察者对象同时监听一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

9.观察者模式(Observer) - 图1

2.2发布订阅模式

发布订阅模式理念和观察者模式相同,但是处理方式上不同。
在发布订阅模式中,发布者和订阅者不知道对方的存在,他们通过调度中心串联起来。
订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(并携带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

9.观察者模式(Observer) - 图2

2.3两者异同点

  • 观察者模式中,观察者知道发布者是谁,并发布者保持对观察者进行记录。而发布订阅模式中,发布者和订阅者不知道对方的存在。它们只是通过调度中心进行通信。
  • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
  • 观察者模式大多是同步,如当事件触发,发布者就会去调用观察者的方法。而发布订阅模式大多是异步的(使用消息队列)。
  • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

尽管存在差异,但也有人说发布-订阅模式是观察者模式的变异,因为它们概念上相似。

2.4两者优缺点

相同优点:

  • 都可以一对多
  • 程序便于扩展

不同优点:

  • 观察者模式:单向解耦,发布者不需要清楚订阅者何时何地订阅,只需要维护订阅队列,发送消息即可
  • 发布订阅模式:双向解耦,发布者和订阅者都不用清楚对方,全部由订阅中心做处理

缺点:

  • 如果一个被观察者和多个观察者的话,会增加维护的难度,并且会消耗很多时间。
  • 如果观察者和发布者之间有循环依赖,可能会导致循环调用引起系统奔溃。
  • 观察者无法得知观察的目标对象是如何发生变化,只能知道目标对象发生了变化。
  • 发布订阅模式,中心任务过重,一旦崩溃,所有订阅者都会受到影响。

4.基本案例

我们平常一直使用的给DOM节点绑定事件,也是观察者模式的案例:

  1. document.body.addEventListener('click', function(){
  2. alert('ok');
  3. },false);
  4. document.body.click();

这里我们订阅了document.bodyclick事件,当body点击它就向订阅者发送消息,就会弹框ok。我们也可以添加很多的订阅。

4.观察者模式 案例

本案例来自 javascript 观察者模式和发布订阅模式

  1. class Dom {
  2. constructor() {
  3. // 订阅事件的观察者
  4. this.events = {}
  5. }
  6. /**
  7. * 添加事件的观察者
  8. * @param {String} event 订阅的事件
  9. * @param {Function} callback 回调函数(观察者)
  10. */
  11. addEventListener(event, callback) {
  12. if (!this.events[event]) {
  13. this.events[event] = []
  14. }
  15. this.events[event].push(callback)
  16. }
  17. /**
  18. * 移除事件的观察者
  19. * @param {String} event 订阅的事件
  20. * @param {Function} callback 回调函数(观察者)
  21. */
  22. removeEventListener(event, callback) {
  23. if (!this.events[event]) {
  24. return
  25. }
  26. const callbackList = this.events[event]
  27. const index = callbackList.indexOf(callback)
  28. if (index > -1) {
  29. callbackList.splice(index, 1)
  30. }
  31. }
  32. /**
  33. * 触发事件
  34. * @param {String} event
  35. */
  36. fireEvent(event) {
  37. if (!this.events[event]) {
  38. return
  39. }
  40. this.events[event].forEach(callback => {
  41. callback()
  42. })
  43. }
  44. }
  45. const handler = () => {
  46. console.log('fire click')
  47. }
  48. const dom = new Dom()
  49. dom.addEventListener('click', handler)
  50. dom.addEventListener('move', function() {
  51. console.log('fire click2')
  52. })
  53. dom.fireEvent('click')

5.发布订阅模式 案例

本案例来自 javascript 观察者模式和发布订阅模式

  1. class EventChannel {
  2. constructor() {
  3. // 主题
  4. this.subjects = {}
  5. }
  6. hasSubject(subject) {
  7. return !!this.subjects[subject]
  8. }
  9. /**
  10. * 订阅的主题
  11. * @param {String} subject 主题名称
  12. * @param {Function} callback 回调方法
  13. */
  14. on(subject, callback) {
  15. if (!this.hasSubject(subject)) {
  16. this.subjects[subject] = []
  17. }
  18. this.subjects[subject].push(callback)
  19. }
  20. /**
  21. * 取消订阅
  22. * @param {String} subject 主题名称
  23. * @param {Function} callback 回调方法
  24. */
  25. off(subject, callback) {
  26. if (!this.hasSubject(subject)) {
  27. return
  28. }
  29. const callbackList = this.subjects[subject]
  30. const index = callbackList.indexOf(callback)
  31. if (index > -1) {
  32. callbackList.splice(index, 1)
  33. }
  34. }
  35. /**
  36. * 发布主题
  37. * @param {String} subject 主题名称
  38. * @param {Argument} data 参数
  39. */
  40. emit(subject, ...data) {
  41. if (!this.hasSubject(subject)) {
  42. return
  43. }
  44. this.subjects[subject].forEach(callback => {
  45. callback(...data)
  46. })
  47. }
  48. }
  49. const channel = new EventChannel()
  50. channel.on('update', function(data) {
  51. console.log(`update value: ${data}`)
  52. })
  53. channel.emit('update', 123)

6. 补充

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

该模式主要解决一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

  1. interface Observer {
  2. notify: Function
  3. };
  4. class Subject {
  5. private observers: Observer[] = [];
  6. public addObserver(observer: Observer): void {
  7. console.log("add one", observer);
  8. this.observers.push(observer);
  9. }
  10. public deleteObserver(observer: Observer): void{
  11. console.log("delete one", observer);
  12. const n: number = this.observers.indexOf(observer);
  13. n != -1 && this.observers.splice(n, 1);
  14. }
  15. public notifyObservers(): void{
  16. console.log("notify:", this.observers);
  17. this.observers.forEach(observer => observer.notify())
  18. }
  19. }
  20. class ConcreteObserver implements Observer {
  21. constructor(private name: string) { }
  22. notify():void {
  23. console.log(`${this.name} has been notified!`)
  24. }
  25. }
  26. function show(): void{
  27. const subject: Subject = new Subject();
  28. subject.addObserver(new ConcreteObserver("leo"));
  29. subject.addObserver(new ConcreteObserver("pingan"));
  30. subject.notifyObservers();
  31. subject.deleteObserver(new ConcreteObserver("leo2"));
  32. subject.notifyObservers();
  33. }
  34. show();

参考资料

  1. 《JavaScript Patterns》
  2. 《TypeScript 设计模式之观察者模式》