掌握观察者模式和发布订阅模式的实现和应用场景。
欢迎来到 TypeScript 设计模式系列,该系列介绍了使用 TypeScript 进行 Web 开发的一些有用的设计模式。
之前的文章如下:
设计模式对 web 开发人员非常重要,掌握它们可以让我们写出更好的代码。在这篇文章中,我将使用 TypeScript 来介绍观察者模式和发布订阅模式。
观察者模式
观察者模式在Web世界中应用广泛,MutationObserver
,IntersectionObserver
,PerformanceObserver
,ResizeObserver
,ReportingObserver。
这些API都可以看到观察者模式.此外,该模式还用于事件监控和数据响应(例如当数据发生变化时,页面会自动更新)。
观察者模式定义了一对多的关系,允许多个观察者对象同时监视一个主对象。当主对象的状态发生变化时,所有观察者对象将被通知,以便它们可以自动更新。
观察者模式中有两个主要角色:主体和观察者。
在上面的图中,Subject 是 my article,观察者是 chris1993 和 bytefish. 由于观察者模式支持简单的广播通信,当新文章发布时,所有观察者都会被自动通知。
为了更好地理解下面的代码,让我们先看看相应的 UML 类图:
在上图中,我们使用interface
分别定义了 Observer
和 Subject
接口,用于描述观察者和主体对象。
观察者接口
interface Observer {
notify(article: Article): void;
}
主题接口
interface Subject {
observers: Observer[];
addObserver(observer: Observer): void;
deleteObserver(observer: Observer): void;
notifyObservers(article: Article): void;
}
然后,我们分别定义上述接口的实现类:ConcreteObserver
和 ConcreteSubject
:
ConcreteObserver类
class ConcreteObserver implements Observer {
constructor(private name: string) {}
notify(article: Article) {
console.log(`"Article: ${article.title}" has been sent to ${this.name}.`);
}
}
ConcreteSubject类
class ConcreteSubject implements Subject{
public observers: Observer[] = [];
public addObserver(observer: Observer): void {
this.observers.push(observer);
}
public deleteObserver(observer: Observer): void {
const n: number = this.observers.indexOf(observer);
n != -1 && this.observers.splice(n, 1);
}
public notifyObservers(article: Article): void {
this.observers.forEach((observer) => observer.notify(article));
}
}
下面验证一下对应的函数:
const subject: Subject = new ConcreteSubject();
const chris1993 = new ConcreteObserver("Chris1993");
const bytefish = new ConcreteObserver("Bytefish");
subject.addObserver(chris1993);
subject.addObserver(bytefish);
subject.notifyObservers({
author: "Bytefer",
title: "Observer Pattern in TypeScript",
url: "https://medium.com/***",
});
subject.deleteObserver(bytefish);
subject.notifyObservers({
author: "Bytefer",
title: "Adapter Pattern in TypeScript",
url: "https://medium.com/***",
});
当上述代码运行成功时,终端将输出以下结果:
"Article: Observer Pattern in TypeScript" has been sent to Chris1993.
"Article: Observer Pattern in TypeScript" has been sent to Bytefish.
"Article: Adapter Pattern in TypeScript" has been sent to Chris1993.
目前,我主要写两个主题,JavaScript 和 TypeScript,所以如果我想发布一篇文章,只有对 JavaScript 或 TypeScript 感兴趣的读者才会收到通知,如果我们使用 Observer 模式,我们需要创建两个不同的 Subject,我们也可以使用 Publish-subscribe 模式。
发布-订阅模式
在软件架构中,发布-订阅是一种消息传递范式,其中消息的发送方(称为发布者)不直接将消息发送给特定的接收方(称为订阅者)。相反,发布的消息被分成不同的类别,并发送给不同的订阅者。同样,订阅者可以表达对一个或多个类别的兴趣,并且只接收感兴趣的消息,而不需要知道哪些发布者存在。
发布-订阅模型中主要有三个角色:发布者、渠道和订阅者。
在上图中,Publisher 是 Bytefer,Channels 中的 Topic A 和 Topic B 分别对应于 JavaScript 主题和 TypeScript 主题,Subscriber 是 chris1993、bytefish 等。
让我们基于发布-订阅模式实现一个 EventEmitter:
type EventHandler = (...args: any[]) => any;
class EventEmitter {
private c = new Map<string, EventHandler[]>();
subscribe(topic: string, ...handlers: EventHandler[]) {
let topics = this.c.get(topic);
if (!topics) {
this.c.set(topic, (topics = []));
}
topics.push(...handlers);
}
unsubscribe(topic: string, handler?: EventHandler): boolean {
if (!handler) {
return this.c.delete(topic);
}
const topics = this.c.get(topic);
if (!topics) {
return false;
}
const index = topics.indexOf(handler);
if (index < 0) {
return false;
}
topics.splice(index, 1);
if (topics.length === 0) {
this.c.delete(topic);
}
return true;
}
publish(topic: string, ...args: any[]): any[] | null {
const topics = this.c.get(topic);
if (!topics) {
return null;
}
return topics.map((handler) => {
try {
return handler(...args);
} catch (e) {
console.error(e);
return null;
}
});
}
}
定义了 EventEmitter
类之后,我们可以像这样使用它:
const eventEmitter = new EventEmitter();
eventEmitter.subscribe("ts",
(msg) => console.log(`Received:${msg}`));
eventEmitter.publish("ts", "Observer pattern");
eventEmitter.unsubscribe("ts");
eventEmitter.publish("ts", "Pub-Sub pattern");
当上述代码运行成功时,终端将输出以下结果: Received: Observer pattern
。
在事件驱动架构中,发布订阅模式发挥着重要的作用,该模式的具体实现可以作为事件总线,实现同一系统中不同组件或模块之间的消息通信,对于广泛使用的插件架构,可以用来实现不同插件之间的消息通信。
在阅读完这篇文章后,我希望你对观察者模式和发布订阅模式有一定的了解。
欢迎关注公众号:文本魔术,了解更多