跳到主要内容

设计模式

单例模式

单例设计模式是一种常见的设计模式,用于确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。在 JavaScript 中,实现单例模式通常意味着让一个特定的类或对象在整个程序中只存在一份实例

有时候我们需要确保某个对象在系统中只有一个,比如配置管理器、线程池或者数据库连接池。单例模式能够减少不必要的内存开销,也避免了对象状态的不一致。

比如 Nodejs(Nextjs) 服务中的数据库连接实例全局唯一

import { PrismaClient } from '@prisma/client'

declare global {
var prisma: PrismaClient | undefined
}

export const db = globalThis.prisma || new PrismaClient()

// 防止开发环境的热重载建立过多的数据库连接
// globalThis 不会受到 hot reload 的影响
if (process.env.NODE_ENV !== 'production')
globalThis.prisma = db

以下是 js 中实现单例模式的简易代码,其思路就是只能使用类方法来获取实例,实例存在的话再次使用 new 关键字会报错

class Singleton {
// 可以省略,但方便理解
static instance
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}

constructor() {
// 禁用 new 关键字
if (Singleton.instance) {
throw new Error("Use Singleton.getInstance() instead of new.");
}
// 初始化操作
// ...
}
}

export default Singleton;

// 使用示例
import Singleton from './Singleton';

// 获取单例
const instance = Singleton.getInstance();

// 再次获取单例
const sameInstance = Singleton.getInstance();
console.log(instance === sameInstance) // true

// 直接使用 new 将会抛出错误
try {
const failInstance = new Singleton();
} catch (error) {
console.error(error.message); // 输出: Use Singleton.getInstance() instead of new.
}

简化版,依然是使用类属性缓存实例对象,直接在 constructor 下手

class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance
}
Singleton.instance = this
// 初始化操作
// ...
}
}

const instance = new Singleton();
const sameInstance = new Singleton();

console.log(instance === sameInstance) // true

发布订阅(Pub/Sub)

概念

发布订阅模式是一种使代码解耦的设计模式,也称为 pub/sub 模式。

发布者负责发布事件或消息,而订阅者负责对这些事件或消息作出响应。

当特定事件或条件发生时,发布者会广播消息,而所有注册了对应事件的订阅者都会收到通知并执行相应的函数或操作。

  • 发布者有一些信息,它决定何时分享这些信息。
  • 订阅者对发布者的信息感兴趣,它告诉发布者它想被通知。
  • 当发布者决定发布信息时,所有注册过兴趣的订阅者都会收到通知。

常见应用场景

  1. 事件处理:在前端开发中,DOM 事件监听本质上就是一个发布订阅模式。
  2. 异步编程:比如 Node.js 中的 EventEmitter,实现了事件的监听和触发。
  3. 跨标签页的通信:常用的 BroadcastChannellocalStorage 的变化等都可以类比为发布订阅模式。
  4. 消息队列:在后端系统中,服务之间通过消息队列进行解耦合。

手写 EventBus

用来实现组件间解耦的通信机制,这个通信机制允许一个组件(发布者广播事件,而其他组件(订阅者)可以监听并响应这些事件

主要包含三个API,分别是 onemit/triggeroff

type EventCallback = (data?: any) => void;

class Eventhub {
private queueMap: { [event: string]: EventCallback[] } = {}

// 订阅事件,即保存事件及其任务/回调
on(event: string, fn: EventCallback): void {
this.queueMap[event] = this.queueMap[event] || []
this.queueMap[event].push(fn)
}

// 发布事件,即触发事件,执行任务/回调
emit(event: string, data?: any) {
const fnList = this.queueMap[event] || []
fnList.forEach(fn => fn(data))
}

// 取消订阅事件,即移除事件的某项任务
off(event: string, fn: EventCallback) {
const fnList = this.queueMap[event] || []
const index = fnList.indexOf(fn)
if (index < 0) return
fnList.splice(index, 1)
}
}

const eventHub = new Eventhub()

const callback1 = (e1: any) => {
console.log(e1, '1事件触发了')
}
const callback2 = (e2: any) => {
console.log(e2, '2事件触发了')
}

eventHub.on('click', callback1)

eventHub.on('click', callback2)

eventHub.emit('click', '第一次')

eventHub.off('click', callback1)

eventHub.emit('click', '第二次')

观察者模式

定义了对象之间的一对多依赖,当一个对象状态改变时,所有依赖它的对象都会收到通知

// 主体对象
class Subject {
observers: Observer[]
constructor() {
this.observers = []
}

// 注册观察者
addObserver(observer: Observer) {
this.observers.push(observer)
}

// 移除观察者
removeObserver(name: string) {
const index = this.observers.findIndex(ob => ob.name === name)
if (index !== -1) {
this.observers.splice(index, 1)
}
}

// 通知观察者
notifyObserver(message: string) {
this.observers.forEach(ob => ob.update(message))
}
}

// 观察者
class Observer {
name: string
constructor(name: string) {
this.name = name
}

// 更新的回调
update(message: string) {
console.log(this.name, 'receive message', message)
}
}

const ob1 = new Observer('ob1')
const ob2 = new Observer('ob2')

const sub = new Subject()

sub.addObserver(ob1)
sub.addObserver(ob2)

sub.notifyObserver('state has changed')

sub.removeObserver('ob2')
sub.notifyObserver('state has changed')

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

观察者模式(Observer Pattern)和发布-订阅模式(Publish/Subscribe Pattern)确实在行为上非常相似,因为它们都描述了如何在对象之间建立一种依赖关系,使得当一个对象变化时,依赖它的对象会被自动通知和更新。不过,它们之间有一些关键的区别:

  1. 对象间的关系:
    • 在观察者模式中,观察者(Observer)直接订阅一个具体的主题(Subject)或对象,并直接接收通知。主题保持对观察者的直接引用。
    • 在发布-订阅模式中,发布者(Publisher)和订阅者(Subscriber)不直接通信,而是通过一个通信中介(通常是事件通道或消息代理)来传递消息。订阅者可以订阅中介管理的特定主题,当有新消息时,由中介通知订阅者。
  2. 组件解耦
    • 观察者模式中的主题与观察者之间的耦合程度较高,因为它们需要知道对方的存在。
    • 发布-订阅模式提供了更高的系统解耦,因为发布者和订阅者不需要知道对方的存在。他们只需要通过消息系统来进行交互。
  3. 事件系统的复杂性:
    • 观察者模式通常比较简单,适用于观察者和主题直接相互作用的场合。
    • 发布-订阅模式则通常涉及更复杂的事件处理系统,可能需要处理更多的事件传递、过滤和队列等功能。
  4. 消息传递:
    • 在观察者模式中,主题通常会向所有注册的观察者广播通知。
    • 在发布-订阅模式中,订阅者可以选择性地监听感兴趣的事件,消息中介负责根据订阅者的订阅内容来过滤消息,并将其传递给相应的订阅者。

简而言之,观察者模式更适合较为简单的场景,对象之间的交互比较直接;而发布-订阅模式由于引入了额外的消息中介,能够支持更高级别的异步消息通信和更松耦合的系统架构。