# 观察者和发布订阅模式

观察者模式是软件设计模式的一种。在此种模式中，一个目标物件管理所有相依于它的观察者物件，并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

其实 24 种基本的设计模式中并没有发布订阅模式，发布订阅模式只是观察者模式的一个别称。但是经过时间的沉淀，似乎观察者模式已经强大了起来，已经独立于观察者模式，成为另外一种不同的设计模式。

![img](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/%E8%A7%82%E5%AF%9F%E8%80%85%E5%92%8C%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F-202211091916.jpg)

## 观察者模式

观察者模式一般至少有一个可被观察的对象 Subject ，可以有多个观察者去观察这个对象。当被观察对象的状态发生变化时，会通知所有观察者对象，使它们能够自动更新。

二者的关系是通过被观察者主动建立的，被观察者至少要有三个方法——添加观察者、移除观察者、通知观察者。当被观察者将某个观察者添加到自己的观察者列表后，观察者与被观察者的关联就建立起来了。此后只要被观察者在某种时机触发通知观察者方法时，观察者即可接收到来自被观察者的消息。

优点：响应式。目标变化就会通知观察者，这是观察者最大的有点，也是因为这个优点，观察者模式在前端才会这么出名。

缺点：不灵活。相比订阅发布模式，由于目标和观察者是耦合在一起的，所以观察者模式需要同时引入目标和观察者才能达到响应式的效果；而订阅发布模式只需要引入事件中心，订阅者和发布者可以不再一处。

```javascript
// 被观察者
class Subject {
  constructor() {
    this.obs = [];
  }
  add(ob) {
    this.obs.push(ob);
  }
  remove(ob) {
    this.obs = this.obs.filter(o => o.name !== ob.name);
  }
  notify(message) {
    this.obs.forEach(ob => ob.notified(message));
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }
  notified(message) {
    console.log(`Hellow, ${this.name}. This is ${message}!`);
  }
}

const subject = new Subject();
const observerLi = new Observer('Li');
const observerZhao = new Observer('Zhao');
subject.add(observerLi);
subject.add(observerZhao);
subject.notify('Subject messagge 01');
subject.remove(observerLi);
subject.notify('Subject messagge 02');
```

## 发布订阅模式

与观察者模式相比，发布订阅核心基于一个消息中心来建立整个体系。其中发布者和订阅者不直接进行通信，而是发布者将要发布的消息交由中心管理，订阅者也是根据自己的情况，按需订阅中心中的消息。

订阅者在订阅事件的时候，只关注事件本身，而不关心谁会发布这个事件；发布者在发布事件的时候，只关注事件本身，而不关心谁订阅了这个事件。

优点：灵活。由于订阅发布模式的发布者和订阅者是解耦的，只要引入订阅发布模式的事件中心，无论在何处都可以发布订阅。同时订阅发布者相互之间不影响。

缺点：容易导致代码不好维护，使用不当就会造成数据流混乱。性能消耗更大，订阅发布模式需要维护事件列队，订阅的事件越多，内存消耗越大。

```javascript
// 消息中心
class PubSub {
  constructor() {
    this.pubs = {};
    this.subs = {};
  }
  publish(type, content) {
    this.pubs[type] = this.pubs[type] || [];
    this.pubs[type].push(content);
  }
  subscribe(type, cb) {
    this.subs[type] = this.subs[type] || [];
    this.subs[type].push(cb);
  }
  notify(type) {
    const pubs = this.pubs[type] || [];
    const subs = this.subs[type] || [];
    subs.forEach((cb) => {
      pubs.forEach(msg => cb(msg))
    });
  }
}

// 发布者
class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(content) {
    this.context.publish(this.name, content);
  }
}

// 订阅者
class Subscriber {
  constructor(context) {
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb);
  }
}

const Type1 = 'music';
const Type2 = 'moive';
const pubsub = new PubSub();
const pubA = new Publisher(Type1, pubsub);
const pubB = new Publisher(Type2, pubsub);
const subA = new Subscriber(pubsub);
const subB = new Subscriber(pubsub);
subA.subscribe(Type1, msg => console.log(`I am A. ${msg} is published!`));
subB.subscribe(Type2, msg => console.log(`I am B. ${msg} is published!`));
pubA.publish('music01!');
pubA.publish('music02!');
pubsub.notify(Type1);
pubB.publish('moive01!');
pubsub.notify(Type2);
```

## 观察者 vs 发布订阅

从概念上理解，两者没什么不同，都是通过事件的方式在某个时间点进行触发，让观察者/订阅者可以进行相应的操作。

两者主要是在实现上有所不同：

* 调度：观察者模式是由被观察者调度的；而发布订阅模式是统一由消息中心调度。
* 角色数量：观察者模式只需要 2 个角色：观察者和被观察者，其中被观察者是重点；而发布订阅至少需要 3 个角色：发布者、订阅者和发布订阅中心，其中发布订阅中心是重点。
* 耦合度：观察者模式中目标和观察者是直接关联的，耦合在一起（有些观念说观察者是解耦，解耦的是业务代码，不是目标和观察者本身）；而发布订阅模式是一个事件中心调度模式，订阅者和发布者是没有直接关联的，通过事件中心进行关联，两者是解耦的。
* 使用场景：观察者模式多用于单个目标内部；而发布订阅模式更多的是一种跨目标的模式，比如消息管理。

## 参考资料

[稀土掘金 - 理解【观察者模式】和【发布订阅】的区别](https://juejin.cn/post/6978728619782701087)
