# 观察者和发布订阅模式

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

其实 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lizh.gitbook.io/knowledge/tech/18-guan-cha-zhe-he-fa-bu-ding-yue-mo-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
