本文目录

[[toc]]

单例模式

单例模式 有几个重要的特点:

  • 优点:

    • 保证一个类只有一个实例。(与 module 全局状态 相同
    • 提供了一个全局访问点。
    • 仅在首次请求单例对象时进行初始化。(与 module 全局状态 区分
  • 缺点:

    • 违反单一职责原则。
    • 掩盖不良设计,程序各组件之间耦合度高。
    • 在多线程环境中需要特殊处理,避免多次创建单例对象。
class Singleton {
  /** 全局存储唯一的实例 */
  private static instance: Singleton

  /** 测试是否为同一实例 */
  private connectionCount = 0

  /** 支持使用 new 创建单例 */
  constructor() {
    if (!Singleton.instance) {
      // 如果没有创建过实例,就使用 this 作为实例,保存到静态方法中
      // 这里不能直接从静态方法中 getInstance ,否则会出现死循环
      Singleton.instance = this
    }

    return Singleton.instance
  }

  /** 支持使用静态方法获取单例 */
  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton()
    }
    return Singleton.instance
  }

  public incrementConnection() {
    this.connectionCount++
    return this.connectionCount
  }
}

const instance1 = Singleton.getInstance()
const instance2 = Singleton.getInstance()

console.log('是否为同一实例', instance1 === instance2)

console.log('Connection count:', instance1.incrementConnection()) // 1
console.log('Connection count:', instance1.incrementConnection()) // 2
console.log('Connection count:', instance2.incrementConnection()) // 3

工厂模式

工厂模式 本质上就是一个返回对象的函数,由于 JS 的特殊性,使用箭头函数要比工厂模式更加简单,而使用 new class 则比工厂函数更低内存开销。

  • 优点:

    • 提高了代码的可维护性和可扩展性。
    • 通过工厂方法创建对象,避免了直接使用 new 关键字,降低了代码耦合度。
    • 可以根据需要返回不同类型的对象,增强了代码的灵活性。
  • 缺点:

    • 增加了系统的复杂性,需要额外编写工厂类或工厂方法。
    • 可能会导致类的数量增加,增加系统的维护成本。
    • 在某些情况下,可能会影响性能,因为需要通过工厂方法创建对象。
interface Product {
  operation: () => string
}

class ConcreteProductA implements Product {
  public operation(): string {
    return 'Result of ConcreteProductA'
  }
}

class ConcreteProductB implements Product {
  public operation(): string {
    return 'Result of ConcreteProductB'
  }
}

function createProduct(type: string): Product {
  if (type === 'A') {
    return new ConcreteProductA()
  } else if (type === 'B') {
    return new ConcreteProductB()
  } else {
    throw new Error('Unknown product type')
  }
}

const productA = Creator.createProduct('A')
console.log(productA.operation()) // Result of ConcreteProductA

const productB = Creator.createProduct('B')
console.log(productB.operation()) // Result of ConcreteProductB

策略模式

策略模式 定义了一系列算法,并将每个算法封装起来,使它们可以互换使用。策略模式使得算法可以独立于使用它的客户端而变化。

  • 优点:

    • 你可以在运行时切换对象内的算法。
    • 你可以将算法的实现和使用算法的代码隔离开来。
    • 你可以使用组合来代替继承。
    • 开闭原则。你无需对上下文进行修改就能够引入新的策略。
  • 缺点:

    • 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式会让程序过于复杂。
    • 客户端必须知晓策略间的不同——它需要选择合适的策略。
    • 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象的完全相同,无需借助额外的类和接口来保持代码简洁。
interface Strategy {
  execute: (a: number, b: number) => number
}

class ConcreteStrategyAdd implements Strategy {
  public execute(a: number, b: number): number {
    return a + b
  }
}

class ConcreteStrategySubtract implements Strategy {
  public execute(a: number, b: number): number {
    return a - b
  }
}

class ConcreteStrategyMultiply implements Strategy {
  public execute(a: number, b: number): number {
    return a * b
  }
}

class Context {
  private strategy: Strategy

  constructor(strategy: Strategy) {
    this.strategy = strategy
  }

  public setStrategy(strategy: Strategy) {
    this.strategy = strategy
  }

  public executeStrategy(a: number, b: number): number {
    return this.strategy.execute(a, b)
  }
}

const addStrategy = new ConcreteStrategyAdd()
const context = new Context(addStrategy)
console.log('Add: ', context.executeStrategy(1, 2)) // 1 + 2 = 3

const subtractStrategy = new ConcreteStrategySubtract()
context.setStrategy(subtractStrategy)
console.log('Subtract: ', context.executeStrategy(5, 3)) // 5 - 3 = 2

const multiplyStrategy = new ConcreteStrategyMultiply()
context.setStrategy(multiplyStrategy)
console.log('Multiply: ', context.executeStrategy(5, 2)) // 5 * 2 = 10

代理模式

代理模式 就是为对象提供另一个 API 相同的替代品,用以对对象行为进行控制

  • 优点:

    • 你可以在客户端毫无察觉的情况下控制服务对象。
    • 如果客户端对服务对象的生命周期没有特殊要求,你可以对其生命周期进行管理。
    • 即使服务对象还未准备好或不存在,代理也可以正常工作。
    • 开闭原则。你可以在不对服务对象或客户端做出修改的情况下创建新代理。
  • 缺点:

    • 代码可能会变得复杂,因为需要新建许多类。
    • 服务响应可能会延迟。
interface ThirdPartyTVLib {
  listVideos: () => string[]
  getVideoInfo: (id: string) => string
  downloadVideo: (id: string) => void
}

// 具体的第三方视频服务类,模拟从远程服务获取视频信息
class ThirdPartyTVClass implements ThirdPartyTVLib {
  listVideos(): string[] {
    console.log('Fetching list of videos from remote service...')
    return ['Video1', 'Video2', 'Video3']
  }

  getVideoInfo(id: string): string {
    console.log(`Fetching info for video ${id} from remote service...`)
    return `Info about video ${id}`
  }

  downloadVideo(id: string): void {
    console.log(`Downloading video ${id} from remote service...`)
  }
}

// 缓存代理类,缓存第三方视频服务的数据
class CachedTVClass implements ThirdPartyTVLib {
  private service: ThirdPartyTVLib
  private listCache: string[] | null = null
  private videoCache: { [key: string]: string } = {}
  private needReset: boolean = false

  constructor(service: ThirdPartyTVLib) {
    this.service = service
  }

  // 缓存视频列表
  listVideos(): string[] {
    if (this.listCache === null || this.needReset) {
      this.listCache = this.service.listVideos()
    }
    return this.listCache
  }

  // 缓存视频信息
  getVideoInfo(id: string): string {
    if (!this.videoCache[id] || this.needReset) {
      this.videoCache[id] = this.service.getVideoInfo(id)
    }
    return this.videoCache[id]
  }

  // 缓存视频下载
  downloadVideo(id: string): void {
    if (!this.videoCache[id] || this.needReset) {
      this.service.downloadVideo(id)
    }
  }
}

// 管理视频服务的类
class TVManager {
  protected service: ThirdPartyTVLib

  constructor(service: ThirdPartyTVLib) {
    this.service = service
  }

  // 渲染视频页面
  renderVideoPage(id: string): void {
    const info = this.service.getVideoInfo(id)
    console.log(`Rendering video page with info: ${info}`)
  }

  // 渲染视频列表面板
  renderListPanel(): void {
    const list = this.service.listVideos()
    console.log(`Rendering list panel with videos: ${list.join(', ')}`)
  }

  // 响应用户输入
  reactOnUserInput(): void {
    this.renderVideoPage('Video1')
    this.renderListPanel()
  }
}

// 应用程序类,初始化视频管理器
class Application {
  init(): void {
    const aTVService = new ThirdPartyTVClass()
    const aTVProxy = new CachedTVClass(aTVService)
    const manager = new TVManager(aTVProxy)
    manager.reactOnUserInput()
  }
}

const app = new Application()
app.init()

发布/订阅模式

发布/订阅模式 允许对象通过发布和订阅事件进行通信,而无需显式地相互引用。

  • 优点:

    • 提高了组件之间的松耦合性。
    • 使得事件的发送者和接收者可以独立演化。
    • 提供了一种灵活的方式来处理事件和消息。
  • 缺点:

    • 可能会导致系统的复杂性增加。
    • 如果不加以管理,可能会导致内存泄漏。
    • 调试和跟踪事件流可能会变得困难。
class PubSub {
  constructor() {
    this.messages = {}
    this.listeners = {}
  }

  // 添加发布者
  publish(type, content) {
    const existContent = this.messages[type]
    if (!existContent) {
      this.messages[type] = []
    }
    this.messages[type].push(content)
  }

  // 添加订阅者
  subscribe(type, cb) {
    const existListener = this.listeners[type]
    if (!existListener) {
      this.listeners[type] = []
    }
    this.listeners[type].push(cb)
  }

  // 通知
  notify(type) {
    const messages = this.messages[type]
    const subscribers = this.listeners[type] || []
    subscribers.forEach((cb, index) => cb(messages[index]))
  }
}

class Publisher {
  constructor(name, context) {
    this.name = name
    this.context = context
  }

  publish(type, content) {
    this.context.publish(type, content)
  }
}

class Subscriber {
  constructor(name, context) {
    this.name = name
    this.context = context
  }

  subscribe(type, cb) {
    this.context.subscribe(type, cb)
  }
}

const TYPE_A = 'music'
const TYPE_B = 'movie'
const TYPE_C = 'novel'

const pubsub = new PubSub()

const publisherA = new Publisher('publisherA', pubsub)
publisherA.publish(TYPE_A, 'we are young')
publisherA.publish(TYPE_B, 'the silicon valley')
const publisherB = new Publisher('publisherB', pubsub)
publisherB.publish(TYPE_A, 'stronger')
const publisherC = new Publisher('publisherC', pubsub)
publisherC.publish(TYPE_C, 'a brief history of time')

const subscriberA = new Subscriber('subscriberA', pubsub)
subscriberA.subscribe(TYPE_A, (res) => {
  console.log('subscriberA received', res)
})
const subscriberB = new Subscriber('subscriberB', pubsub)
subscriberB.subscribe(TYPE_C, (res) => {
  console.log('subscriberB received', res)
})
const subscriberC = new Subscriber('subscriberC', pubsub)
subscriberC.subscribe(TYPE_B, (res) => {
  console.log('subscriberC received', res)
})

pubsub.notify(TYPE_A)
pubsub.notify(TYPE_B)
pubsub.notify(TYPE_C)

观察者模式

观察者模式 允许定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。

  • 优点:

    • 开闭原则。你无需修改发布者代码就能引入新的订阅者(如果是发布者接口则可轻松引入发布者类)。
    • 你可以在运行时建立对象之间的联系。
  • 缺点:

    • 订阅者的通知顺序是随机的。
// 发布者基类包含订阅管理代码和通知方法。
class EventManager {
  private listeners: { [eventType: string]: EventListener[] } = {}

  // 订阅事件
  public subscribe(eventType: string, listener: EventListener): void {
    if (!this.listeners[eventType]) {
      this.listeners[eventType] = []
    }
    this.listeners[eventType].push(listener)
  }

  // 取消订阅事件
  public unsubscribe(eventType: string, listener: EventListener): void {
    if (!this.listeners[eventType]) {
      return
    }
    this.listeners[eventType] = this.listeners[eventType].filter(l => l !== listener)
  }

  // 通知所有订阅者
  public notify(eventType: string, data: any): void {
    if (!this.listeners[eventType]) {
      return
    }
    this.listeners[eventType].forEach(listener => listener.update(data))
  }
}

// 具体发布者包含一些订阅者感兴趣的实际业务逻辑。
class Editor {
  public events: EventManager
  private file: File | null = null

  constructor() {
    this.events = new EventManager()
  }

  // 打开文件并通知订阅者
  public openFile(path: string): void {
    this.file = new File(path)
    this.events.notify('open', this.file.name)
  }

  // 保存文件并通知订阅者
  public saveFile(): void {
    if (this.file) {
      this.file.write()
      this.events.notify('save', this.file.name)
    }
  }
}

// 订阅者接口
interface EventListener {
  update: (filename: string) => void
}

// 具体订阅者对更新消息做出响应
class LoggingListener implements EventListener {
  private log: File
  private message: string

  constructor(logFilename: string, message: string) {
    this.log = new File(logFilename)
    this.message = message
  }

  public update(filename: string): void {
    this.log.write(this.message.replace('%s', filename))
  }
}

class EmailAlertsListener implements EventListener {
  private email: string
  private message: string

  constructor(email: string, message: string) {
    this.email = email
    this.message = message
  }

  public update(filename: string): void {
    // 模拟发送邮件
    console.log(`Email to ${this.email}: ${this.message.replace('%s', filename)}`)
  }
}

// 应用程序配置发布者和订阅者
class Application {
  public config(): void {
    const editor = new Editor()

    const logger = new LoggingListener(
      '/path/to/log.txt',
      '有人打开了文件:%s'
    )
    editor.events.subscribe('open', logger)

    const emailAlerts = new EmailAlertsListener(
      'admin@example.com',
      '有人更改了文件:%s'
    )
    editor.events.subscribe('save', emailAlerts)
  }
}