前端设计模式——观察者模式

417 阅读4分钟

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象的状态发生变化时,它会通知所有观察者对象,使它们能够自动更新自己。

在观察者模式中,主题对象相当于被观察者,它维护着一个观察者列表,该列表存储着所有依赖于该主题的观察者对象。当主题对象的状态发生变化时,它会遍历观察者列表,依次调用每个观察者对象的更新方法,从而使所有的观察者对象都能够得到通知,并进行相应的处理。

观察者模式可以用于很多场景,例如:

  • 网页事件绑定,如按钮点击事件、输入框输入事件等;
  • MVC 架构中的视图与模型,当模型发生变化时,视图需要自动更新;
  • 数据库连接池,连接池中的连接对象可以作为被观察者,当连接对象可用时,通知所有观察者。

观察者模式的优点是解耦,使得主题对象和观察者对象之间的耦合度降低,从而使得系统更加灵活。同时,观察者模式也可以实现广播通信,当一个对象发生变化时,可以将消息广播给所有观察者对象,从而避免手动通知每个观察者对象的繁琐工作。

观察者模式与发布订阅模式的区别

发布订阅模式和观察者模式是两种常见的设计模式,它们都是用于实现对象间的松耦合通信。

观察者模式中,观察者对象(也叫订阅者或监听器)直接订阅(注册)主题对象的事件,主题对象在发生变化时主动通知(调用)观察者对象的回调函数,观察者对象只需要关心主题对象的变化即可。

而发布订阅模式中,发布者对象(也叫主题对象)不直接通知订阅者对象,而是把消息交给一个调度中心(也叫消息队列或者事件通道),由调度中心统一分发消息给订阅者对象。订阅者对象需要订阅调度中心,也就是向调度中心注册自己所关心的消息类型,当调度中心有相关消息时,自动通知订阅者对象。

因此,发布订阅模式中增加了一个调度中心,用于解耦发布者和订阅者之间的关系,使得发布者和订阅者都不需要知道对方的存在。观察者模式中,主题对象和观察者对象之间是直接关联的,彼此知道对方的存在。

简单来说,观察者模式是一对一的通信,而发布订阅模式是一对多的通信。

TypeScript 实现

  1. 定义观察者接口(Observer):定义一个接口,该接口中包含一个更新(update)方法,用于通知观察者。
interface Observer {
  update(): void;
}
  1. 定义主题接口(Subject):定义一个接口,该接口中包含添加观察者、删除观察者和通知观察者方法。
interface Subject {
  attach(observer: Observer): void;
  detach(observer: Observer): void;
  notify(): void;
}
  1. 实现具体的观察者(ConcreteObserver):实现 Observer 接口,用于接收和处理通知。
class ConcreteObserver implements Observer {
  update(): void {
    console.log("ConcreteObserver is notified");
  }
}
  1. 实现具体的主题(ConcreteSubject):实现 Subject 接口,用于添加、删除和通知观察者。
class ConcreteSubject implements Subject {
  private observers: Observer[] = [];

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

  detach(observer: Observer): void {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(): void {
    for (const observer of this.observers) {
      observer.update();
    }
  }
}
  1. 使用观察者模式:实例化一个具体主题,然后实例化多个具体观察者,并将它们注册到主题中。当主题发生变化时,调用 notify 方法通知所有的观察者。
const subject = new ConcreteSubject();
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();

subject.attach(observer1);
subject.attach(observer2);

subject.notify();
// Output:
// ConcreteObserver is notified
// ConcreteObserver is notified

subject.detach(observer1);

subject.notify();
// Output:
// ConcreteObserver is notified

以上是一个简单的 TypeScript 实现观察者模式的例子。需要注意的是,在实际应用中可能需要更多的代码来处理各种复杂情况,比如观察者的执行顺序等。

JavaScript 实现

// 定义观察者对象
function Observer() {
  this.handlers = [];
}

Observer.prototype = {
  subscribe: function(fn) {
    this.handlers.push(fn);
  },
  unsubscribe: function(fn) {
    this.handlers = this.handlers.filter(
      function(item) {
        if (item !== fn) {
          return item;
        }
      }
    );
  },
  notify: function(msg) {
    this.handlers.forEach(function(fn) {
      fn(msg);
    });
  }
}

// 定义被观察者对象
function Subject() {
  this.observers = new Observer();
}

Subject.prototype = {
  addListener: function(fn) {
    this.observers.subscribe(fn);
  },
  removeListener: function(fn) {
    this.observers.unsubscribe(fn);
  },
  notify: function(msg) {
    this.observers.notify(msg);
  }
}

// 创建被观察者对象
var subject = new Subject();

// 创建观察者对象
var observer1 = function(msg) {
  console.log('Observer 1 received:', msg);
}

var observer2 = function(msg) {
  console.log('Observer 2 received:', msg);
}

// 添加观察者
subject.addListener(observer1);
subject.addListener(observer2);

// 发送通知
subject.notify('Hello, World!');

// 移除观察者
subject.removeListener(observer2);

// 发送通知
subject.notify('Hello, World again!');

在上面的代码中,Observer 对象表示观察者,Subject 对象表示被观察者。Observer 对象有 subscribeunsubscribenotify 三个方法,分别用于订阅、取消订阅和通知观察者。Subject 对象有 addListenerremoveListenernotify 三个方法,分别用于添加观察者、移除观察者和通知被观察者。通过这种方式,观察者可以订阅被观察者,并在其状态发生改变时得到通知。