解密观察者模式

86 阅读3分钟

概念

一种一对多的关系,多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象

解决的问题

  1. 需要定义对象之间的一对多依赖关系,而不使对象之间紧密耦合。
  2. 当一个对象改变状态时,需要自动更新一组依赖对象。

需要注意的问题

内存泄漏(已过期的监听器问题)

在观察者模式的基本实现中,当观察者注册到主题时,主题保留了对观察者的引用,以便在状态变化时通知它们。但如果在后续的使用中,没有正确注销(取消注册)观察者,那么这些观察者对象将保持活动状态,无法被垃圾回收器回收,从而导致内存泄漏。

显示注册

// 创建主题对象
class Subject {
  constructor() {
    this.observers = []; // 观察者列表
  }
​
  // 注册观察者
  registerObserver(observer) {
    this.observers.push(observer);
  }
​
  // 注销观察者
  unregisterObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }
​
  // 通知观察者状态变化
  notifyObservers() {
    for (const observer of this.observers) {
      observer.update();
    }
  }
}
​
// 创建观察者对象
class Observer {
  constructor() {
    // ...
  }
​
  // 观察者的更新操作
  update() {
    // ...
  }
}
​
// 使用强引用创建主题和观察者
const subject = new Subject();
const observer = new Observer();
​
// 注册观察者到主题
subject.registerObserver(observer);
​
// 当主题状态发生变化时,通知观察者
subject.notifyObservers();
​
// 有一天突然不想要这个观察者了
// 忘记注销观察者/没有注销观察者的方法 
// 然后就寄了

当使用弱引用时,对象之间的引用关系是非强制性的,即当没有其他强引用指向对象时,对象可能会被垃圾回收器回收。以下是使用弱引用的JavaScript示例代码:

// 使用 WeakMap 创建主题对象
class Subject {
  constructor() {
    this.observers = new WeakMap(); // 使用 WeakMap 存储观察者
  }
​
  // 注册观察者
  registerObserver(observer) {
    this.observers.set(observer, true);
  }
​
  // 通知观察者状态变化
  notifyObservers() {
    for (const observer of this.observers.keys()) {
      observer.update();
    }
  }
}
​
// 创建观察者对象
class Observer {
  constructor() {
    // ...
  }
​
  // 观察者的更新操作
  update() {
    // ...
  }
}
​
// 使用弱引用创建主题和观察者
const subject = new Subject();
const observer = new Observer();
​
// 注册观察者到主题
subject.registerObserver(observer);
​
// 当主题状态发生变化时,通知观察者
subject.notifyObservers();

无响应

在模型状态频繁更新的情况下。频繁的更新可能导致视图变得不响应(例如,调用了许多重绘方法),这时观察者应该整个节流。

// 创建主题对象
class Subject {
  constructor() {
    this.observers = []; // 观察者列表
    this.timerId = null; // 定时器ID
  }
​
  // 注册观察者
  registerObserver(observer) {
    this.observers.push(observer);
  }
​
  // 通知观察者状态变化
  // 整个节流
  notifyObservers() {
    if (!this.timerId) {
      this.timerId = setTimeout(() => {
        for (const observer of this.observers) {
          observer.update();
        }
        this.timerId = null; // 清空定时器ID
      }, 1000); // 设置一个1秒的间隔,控制视图更新频率
    }
  }
}
​
// 创建观察者对象
class Observer {
  constructor() {
    // ...
  }
​
  // 观察者的更新操作
  update() {
    // ...
  }
}
​
const subject = new Subject();
const observer = new Observer();
​
subject.registerObserver(observer);
​
// 模拟频繁的状态更新
setInterval(() => {
  subject.notifyObservers();
}, 100); // 每100毫秒更新一次状态

异常处理

如果顺序执行的话,只要一个观察者出现错误,就卡住了。唔这方面我只想到了try catch一下,不太知道还有什么别的解决方式。

与之相关的设计模式

发布订阅模式,中介者模式,单例模式.....应该后续会填坑的吧(心虚