从前端视角看设计模式之行为型模式篇

上篇我们介绍了 设计模式之结构型模式篇,接下来介绍设计模式之行为型模式篇

责任链模式

责任链模式允许将请求沿着一条链传递,直到有一个对象处理它为止。每个处理者都有机会处理该请求,或者将其传递给链中的下一个处理者,每个处理者只关心自己能处理的请求,而不关心请求的来源和链中的其他处理者


它的使用场景如下:

1)当有多个对象可以处理请求,且具体由哪个对象处理由运行时决定时

2)当需要向多个对象中的一个提交请求,而不想明确指定接收者时

3)在实际应用中,广泛应用于权限管理、审批流程等场景


责任链模式包含以下几个主要角色:

1)抽象处理者

负责定义处理请求的接口,通常包含一个指向下一个处理者的引用,若不能处理请求,则将请求传递给下一个处理者

2)具体处理者

继承自抽象处理者,实现具体的请求处理逻辑

3)客户端

向链上的处理者发出请求


通过以下这个审批系统来理解责任链模式,涉及多个审批角色,如经理、总监、CEO,每个角色都有不同的审批权限

1)定义请求类

包含请求的内容

复制代码
// 定义请求类
class Request {
    constructor(amount, description) {
      this.amount = amount
      this.description = description
    }
}

2)定义抽象处理者

声明一个方法来处理请求,并定义一个指向下一个处理者的引用

复制代码
// 定义抽象处理者类
class Approver {
    constructor(name) {
      this.name = name
      this.nextApprover = null // 下一位审批者
    }
    setNext(approver) {
      this.nextApprover = approver
    }
    // 处理请求的方法,具体逻辑由子类实现
    approve(request) {
      if (this.nextApprover) {
        this.nextApprover.approve(request)
      } else {
        console.log('没有审批者可以处理这个请求')
      }
    }
}

3)定义具体处理者

实现请求的处理逻辑,若无法处理则交给下一个处理者

复制代码
// 定义具体处理者类:经理、总监、CEO
class Manager extends Approver {
    approve(request) {
      if (request.amount <= 1000) {
        console.log(`${this.name} 批准了 ${request.description},金额: ${request.amount}`)
      } else if (this.nextApprover) {
        console.log(`${this.name} 无权批准该请求,转交给 ${this.nextApprover.name}`)
        this.nextApprover.approve(request)
      }
    }
}
class Director extends Approver {
    approve(request) {
      if (request.amount <= 5000) {
        console.log(`${this.name} 批准了 ${request.description},金额: ${request.amount}`)
      } else if (this.nextApprover) {
        console.log(`${this.name} 无权批准该请求,转交给 ${this.nextApprover.name}`)
        this.nextApprover.approve(request)
      }
    }
} 
class CEO extends Approver {
    approve(request) {
      console.log(`${this.name} 批准了 ${request.description},金额: ${request.amount}`)
    }
}

4)客户端

创建并发出请求

复制代码
// 客户端代码
const manager = new Manager('经理')
const director = new Director('总监')
const ceo = new CEO('CEO')

// 设定审批链:经理 -> 总监 -> CEO
manager.setNext(director)
director.setNext(ceo)

// 发起请求
const request1 = new Request(500, '购买办公设备')
manager.approve(request1)

const request2 = new Request(3000, '购买电脑')
manager.approve(request2)

const request3 = new Request(10000, '购买企业级服务器')
manager.approve(request3)

执行代码,运行结果如下:

命令模式

命令模式将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作


它的使用场景如下:

1)当需要对行为进行记录、撤销/重做或事务处理时


命令模式包含以下几个主要角色:

1)命令

命令接口,声明一个执行操作的方法

2)具体命令

实现命令接口,负责执行具体操作,通常包含对接收者的引用,通过调用接收者的方法来完成请求的处理

3)接收者

知道如何执行与请求相关的操作,实际执行命令的对象

4)调用者

发送命令的对象,它包含了一个命令对象并能触发命令的执行,调用者并不直接处理请求,而是通过将请求传递给命令对象来实现

5)客户端

创建具体命令对象并设置其接收者,将命令对象交给调用者执行


通过以下这个遥控器控制家电来理解命令模式,将每一个开关的操作封装成命令对象,并通过遥控器来调用这些命令

1)命令接口

定义一个命令接口,包含一个执行方法execute()

复制代码
// 定义命令接口
class Command {
    execute() {
      throw new Error("execute() 必须被实现")
    }
}

2)具体命令

实现命令接口,具体实现每个命令的操作

复制代码
// 具体命令 - 开灯命令
class LightOnCommand extends Command {
    constructor(light) {
      super()
      this.light = light
    }
    execute() {
      this.light.turnOn()
    }
}
// 具体命令 - 关灯命令
class LightOffCommand extends Command {
    constructor(light) {
      super()
      this.light = light
    }
    execute() {
      this.light.turnOff()
    }
}
// 具体命令 - 开风扇命令
class FanOnCommand extends Command {
    constructor(fan) {
      super()
      this.fan = fan
    }
    execute() {
      this.fan.turnOn()
    }
}
// 具体命令 - 关风扇命令
class FanOffCommand extends Command {
    constructor(fan) {
      super()
      this.fan = fan
    }
    execute() {
      this.fan.turnOff()
    }
}

3)接收者

具体的家电设备(灯和风扇),每个设备都有开和关的操作

复制代码
// 接收者 - 灯类
class Light {
    turnOn() {
      console.log("灯已打开")
    }
    turnOff() {
      console.log("灯已关闭")
    }
}
// 接收者 - 风扇类
class Fan {
    turnOn() {
      console.log("风扇已打开")
    }
    turnOff() {
      console.log("风扇已关闭")
    }
}

4)调用者

遥控器,通过调用命令对象的execute()方法来执行命令

复制代码
// 调用者 - 遥控器类
class RemoteControl {
    constructor() {
      this.command = null
    }
    setCommand(command) {
      this.command = command
    }
    pressButton() {
      this.command.execute()
    }
}

5)客户端

客户端创建命令对象,并设置对应的设备

复制代码
// 客户端代码
const light = new Light()
const fan = new Fan()

// 创建命令对象
const lightOn = new LightOnCommand(light)
const lightOff = new LightOffCommand(light)
const fanOn = new FanOnCommand(fan)
const fanOff = new FanOffCommand(fan)

// 创建遥控器
const remote = new RemoteControl()

// 按下按钮执行开关命令
remote.setCommand(lightOn)
remote.pressButton()  // 灯已打开

remote.setCommand(fanOn)
remote.pressButton()  // 风扇已打开

remote.setCommand(lightOff)
remote.pressButton()  // 灯已关闭

remote.setCommand(fanOff)
remote.pressButton()  // 风扇已关闭

解释器模式

解释器模式用于给定语言的句法(语法规则)提供一个解释器,这个解释器使用该表示来解释语言中的句子


它的使用场景如下:

1)当某一特定类型的问题频繁出现,并且可以通过一种简单的语言来表达这些问题的实例时

2)应用于编程语言的解释器、数学表达式计算、规则引擎


它的优缺点:

1)优点

  • 适用于需要解释计算规则的场景,如小型语言解析、脚本语言等
  • 可以通过扩展表达式类层次来增加新的语法规则,而不影响其他类

2)缺点

  • 当文法非常复杂时,解释器模式会产生非常庞大的类层次结构,难以维护
  • 性能较低,因为每次计算都需要遍历语法树

解释器模式包含以下几个主要角色:

1)抽象表达式

每个解释器的角色,通常是一个抽象类或接口,声明了解释方法

2)终结符表达式

实现抽象表达式接口的具体类,用于解释基本的语法规则

3)非终结符表达式

也是实现抽象表达式接口的具体类,用于处理复合的语法规则

4)上下文

包含解释过程中需要的全局信息,如环境数据

5)客户端

通过构建抽象表达式对象,并将表达式组合成语法树来使用解释器


通过以下这个数学表达式计算来理解解释器模式,通过解释器模式实现一个简单的计算器,解析并计算表达式

1)定义抽象表达式

每个表达式都要实现一个interpret方法

复制代码
// 抽象表达式
class Expression {
    interpret(context) {
      throw new Error("必须实现 interpret 方法")
    }
}

2)实现终结符表达式

如数字和操作符

复制代码
// 终结符表达式 - 数字
class NumberExpression extends Expression {
    constructor(value) {
      super()
      this.value = value
    }
    interpret(context) {
      return this.value
    }
}

3)实现非终结符表达式

如加法和减法的组合表达式

复制代码
// 非终结符表达式 - 加法
class AddExpression extends Expression {
    constructor(left, right) {
      super()
      this.left = left  // 左操作数
      this.right = right // 右操作数
    }
    interpret(context) {
      return this.left.interpret(context) + this.right.interpret(context)
    }
}
// 非终结符表达式 - 减法
class SubtractExpression extends Expression {
    constructor(left, right) {
      super()
      this.left = left
      this.right = right
    }
    interpret(context) {
      return this.left.interpret(context) - this.right.interpret(context)
    }
}

4)客户端

构建表达式并调用解释器来计算结果

复制代码
// 客户端代码
function evaluateExpression() {
    // 构建表达式树
    const expression = new AddExpression(
      new NumberExpression(5),
      new SubtractExpression(new NumberExpression(10), new NumberExpression(3))
    )
    // 执行计算
    const result = expression.interpret()
    console.log(`计算结果: ${result}`)  // 5 + (10 - 3) = 12
}

// 执行计算
evaluateExpression()

迭代器模式

迭代器模式提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该集合对象的内部表示


它的使用场景如下:

1)需要顺序访问集合中的元素:例如,遍历列表、队列、栈等容器中的元素

2)不想暴露集合的内部实现时

3)需要支持多种不同方式遍历集合:可以为集合对象提供多种不同的迭代器,支持不同的遍历策略


迭代器模式包括以下几个主要角色:

1)迭代器

定义了访问元素的接口,允许遍历集合中的元素

2)具体迭代器

实现迭代器接口,提供了遍历集合元素的具体方式

3)聚合接口

定义了创建迭代器的接口,通常会有一个createIterator()方法,用来返回一个迭代器对象

4)具体聚合类

实现聚合接口,返回一个具体的迭代器,通常是集合对象


通过以下这个例子来理解迭代器模式,使用迭代器模式来遍历书架上的书

1)定义迭代器接口

定义了迭代器的基本接口方法hasNext()next()

复制代码
// 迭代器接口
class Iterator {
    hasNext() {
      throw new Error('必须实现 hasNext 方法')
    }
    next() {
      throw new Error('必须实现 next 方法')
    }
}

2)定义具体迭代器

实现了Iterator接口,提供了书架上书籍的遍历功能

hasNext()方法检查是否还有书籍,next() 方法返回当前书籍并将索引移到下一本书

复制代码
// 具体迭代器
class BookIterator extends Iterator {
    constructor(bookShelf) {
      super()
      this.bookShelf = bookShelf
      this.index = 0 // 从第一个元素开始
    }
    hasNext() {
      return this.index < this.bookShelf.books.length
    }
    next() {
      if (this.hasNext()) {
        return this.bookShelf.books[this.index++]
      } else {
        return null
      }
    }
}

3)定义聚合接口

定义了聚合接口,声明了创建迭代器的方法

复制代码
// 聚合接口
class Aggregate {
    createIterator() {
      throw new Error('必须实现 createIterator 方法')
    }
}

4)定义具体聚合类

具体的聚合类,实现了Aggregate接口,提供了书籍的存储和管理,并实现了createIterator()来返回一个具体的迭代器对象

复制代码
// 具体聚合类 - 书架
class BookShelf extends Aggregate {
    constructor() {
      super()
      this.books = [] // 用于存储书籍
    }
    addBook(book) {
      this.books.push(book)
    }
    createIterator() {
      return new BookIterator(this)
    }
}

5)客户端

创建了一个BookShelf实例,添加了一些书籍,使用BookIterator来遍历这些书籍

复制代码
// 客户端代码
function clientCode() {
    // 创建一个书架实例并添加一些书籍
    const bookShelf = new BookShelf()
    bookShelf.addBook("《设计模式》")
    bookShelf.addBook("《JavaScript红宝书》")
    bookShelf.addBook("《前端开发实践》")
  
    // 创建迭代器并遍历书架上的书籍
    const iterator = bookShelf.createIterator()
    
    while (iterator.hasNext()) {
      console.log(iterator.next()) // 输出书籍名称
    }
}

// 执行客户端代码
clientCode()

中介者模式

中介者模式通过定义一个中介者对象来封装一组对象之间的交互,这些对象之间不需要直接通信,而是通过中介者来协调它们的行为


它的使用场景如下:

1)多个对象之间需要协作:当系统中多个对象之间的交互比较复杂时

2)实现系统的解耦:特别适用于对象之间依赖关系较复杂的系统


中介者模式包含以下几个主要角色:

1)中介者

定义了一个接口,所有的通信都通过中介者进行,管理和协调各个同事对象之间的通信

2)具体中介者

实现了中介者接口,具体实现如何协调各个同事对象之间的交互

3)同事类

每个同事对象都知道中介者,并通过中介者来进行交互

4)具体同事类

继承同事类,定义了具体的行为,且通过中介者与其他同事类进行通信


通过以下这个聊天室系统来理解中介者模式,多个用户之间进行通信,使用中介者模式来管理用户之间的消息传递

1)定义中介者接口

定义了中介者接口,所有的通信都通过sendMessage()方法来完成

复制代码
// 中介者接口
class Mediator {
    sendMessage(message, colleague) {
      throw new Error('sendMessage 方法必须实现')
    }
}

2)定义具体中介者

实现了Mediator接口,维护了一个用户列表,并负责转发消息给所有其他用户

复制代码
// 具体中介者
class ChatRoomMediator extends Mediator {
    constructor() {
      super()
      this.users = []
    }
    addUser(user) {
      this.users.push(user)
    }
    sendMessage(message, colleague) {
      this.users.forEach(user => {
        // 除了发送消息的用户,其他用户都能接收到消息
        if (user !== colleague) {
          user.receiveMessage(message)
        }
      })
    }
}

3)定义同事类

每个用户对象通过中介者进行通信,它知道如何发送和接收消息,但不与其他用户直接交互

复制代码
// 同事类
class User {
    constructor(name, mediator) {
      this.name = name
      this.mediator = mediator
    }
    sendMessage(message) {
      this.mediator.sendMessage(message, this)
    }
    receiveMessage(message) {
      console.log(`${this.name} 收到消息: ${message}`)
    }
}

4)客户端代码

创建一个ChatRoomMediator,并在其中添加多个用户。每个用户发送消息时,通过中介者转发给其他用户

复制代码
// 客户端代码
function clientCode() {
    // 创建一个聊天室中介者
    const chatRoomMediator = new ChatRoomMediator()
  
    // 创建一些用户
    const user1 = new User('Alice', chatRoomMediator)
    const user2 = new User('Bob', chatRoomMediator)
    const user3 = new User('Charlie', chatRoomMediator)
  
    // 将用户添加到聊天室中介者中
    chatRoomMediator.addUser(user1)
    chatRoomMediator.addUser(user2)
    chatRoomMediator.addUser(user3)
  
    // 用户之间发送消息
    user1.sendMessage('Hello, everyone!')
    user2.sendMessage('Hi, Alice!')
    user3.sendMessage('Good morning, all!')
}

// 执行客户端代码
clientCode()

执行代码,运行结果如下:

备忘录模式

备忘录模式允许在不改变对象的内部结构的情况下,保存和恢复对象的状态


它的使用场景如下:

1)需要保存对象的某个状态:在特定情况下,系统中某些对象的状态可能需要保存,以便之后恢复,比如撤销操作

2)需要历史记录管理:当系统需要多次保存状态,并且支持恢复到某个历史状态时


备忘录模式包含以下几个主要角色:

1)发起人

负责创建备忘录对象,并通过备忘录对象存储自己的内部状态

2)备忘录

负责存储发起人的内部状态,该对象只能被发起人访问,防止外部类修改状态

3)管理者

负责管理备忘录对象,管理者不能修改备忘录的内容,只能将备忘录存储或恢复


通过以下这个文本管理器来理解备忘录模式,用户在编辑过程中可能需要撤销和恢复文本的内容,使用备忘录模式来保存文本的状态

1)定义发起人

保存文本内容并创建备忘录

复制代码
// 发起人
class TextEditor {
    constructor() {
      this.text = ""
    }
    // 设置文本内容
    setText(text) {
      this.text = text
    }
    // 获取当前文本内容
    getText() {
      return this.text
    }
    // 创建一个备忘录对象,保存当前文本内容
    createMemento() {
      return new Memento(this.text)
    }
    // 恢复文本内容,从备忘录中读取
    restoreFromMemento(memento) {
      this.text = memento.getSavedText()
    }
}

2)定义备忘录

保存文本内容

复制代码
// 备忘录类
class Memento {
    constructor(text) {
      this.text = text
    }
    // 获取保存的文本
    getSavedText() {
      return this.text
    }
}

3)定义管理者

负责保存和恢复备忘录

复制代码
// 管理者类
class Caretaker {
    constructor() {
      this.mementos = []
    }
    // 保存备忘录
    saveMemento(memento) {
      this.mementos.push(memento)
    }
    // 恢复备忘录
    restoreMemento() {
      return this.mementos.pop()
    }
}

4)客户端

复制代码
// 客户端代码
function clientCode() {
    const textEditor = new TextEditor()
    const caretaker = new Caretaker()
  
    // 用户输入文本
    textEditor.setText("Hello")
    console.log("当前文本:", textEditor.getText())
  
    // 保存当前文本状态
    caretaker.saveMemento(textEditor.createMemento())
  
    // 用户继续编辑
    textEditor.setText("Hello, World!")
    console.log("当前文本:", textEditor.getText())
  
    // 保存新的文本状态
    caretaker.saveMemento(textEditor.createMemento())
  
    // 用户继续编辑
    textEditor.setText("Hello, World! How are you?")
    console.log("当前文本:", textEditor.getText())
  
    // 恢复到之前的状态
    textEditor.restoreFromMemento(caretaker.restoreMemento())
    console.log("恢复到之前的状态:", textEditor.getText())
  
    // 再次恢复到更早的状态
    textEditor.restoreFromMemento(caretaker.restoreMemento())
    console.log("恢复到更早的状态:", textEditor.getText())
}

// 执行客户端代码
clientCode()

执行代码,运行结果如下:

观察者模式

观察者模式定义了一种一对多的依赖关系 ,让多个观察者对象同时监听某一个主题对象,这个主题对象一旦发生变化,所有依赖于它的观察者都会自动收到通知并更新


它的使用场景如下:

1)事件驱动:当一个对象的状态变化需要通知多个对象时

2)实时系统:例如新闻订阅系统、天气预报系统等,需要将变化实时通知到所有相关的观察者


观察者模式包含以下几个主要角色:

1)主题

具有状态的对象,维护着一个观察者列表,并提供了添加、删除和通知观察者的方法

2)观察者

接收主题通知的对象,需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作

3)具体主题

主题的具体实现类,维护着观察者列表,并在状态发生改变时通知观察者

4)具体观察者

观察者的具体实现类,实现了更新方法,定义了在收到主题通知时需要执行的具体操作


通过以下这个简单的天气预报系统来理解观察者模式,天气信息变化时,会通知所有注册的订阅者(观察者)来更新显示的信息

1)定义观察者接口

复制代码
// 观察者接口
class Observer {
    update(weatherData) {
      throw new Error("update方法需要在子类中实现")
    }
}

2)定义主题类

负责管理观察者并通知它们

复制代码
// 主题类
class WeatherStation {
    constructor() {
      this.observers = []  // 用来存储所有注册的观察者
      this.weatherData = null  // 存储天气数据
    }
    // 注册观察者
    addObserver(observer) {
      this.observers.push(observer)
    }
    // 注销观察者
    removeObserver(observer) {
      const index = this.observers.indexOf(observer)
      if (index !== -1) {
        this.observers.splice(index, 1)
      }
    }
    // 通知所有观察者
    notifyObservers() {
      for (let observer of this.observers) {
        observer.update(this.weatherData)
      }
    }
    // 更新天气数据,并通知观察者
    setWeatherData(data) {
      this.weatherData = data
      this.notifyObservers()
    }
}

3)定义具体观察类

定义WeatherAppWeatherWebsite两个具体的观察类,显示天气数据

复制代码
// 具体的观察者类
class WeatherApp extends Observer {
    update(weatherData) {
      console.log(`WeatherApp: 当前天气是:${weatherData}`)
    }
}
class WeatherWebsite extends Observer {
    update(weatherData) {
      console.log(`WeatherWebsite: 当前天气是:${weatherData}`)
    }
}

4)客户端

复制代码
// 客户端代码
function clientCode() {
    const weatherStation = new WeatherStation()
  
    // 创建观察者
    const app = new WeatherApp()
    const website = new WeatherWebsite()
  
    // 注册观察者
    weatherStation.addObserver(app)
    weatherStation.addObserver(website)
  
    // 发布新的天气数据
    console.log("设置天气为:晴天")
    weatherStation.setWeatherData("晴天")
  
    console.log("\n设置天气为: 雨天")
    weatherStation.setWeatherData("雨天")
  
    // 注销观察者
    weatherStation.removeObserver(website)
  
    console.log("\n设置天气为: 多云")
    weatherStation.setWeatherData("多云")
}
// 执行客户端代码
clientCode()

执行代码,运行结果如下:

状态模式

状态模式允许对象在内部状态改变时改变其行为,对象的行为取决于其当前的状态,该模式的关键是将对象的状态封装成独立的类,并让对象在内部根据状态来决定具体的行为, 可以避免使用大量的条件语句来实现状态切换


它的使用场景如下:

1)状态变化频繁:当一个对象的状态变化比较频繁时

2)状态依赖:当对象的行为取决于其状态时,状态模式可以避免使用大量的条件语句

3)行为变化:如果系统中一个对象的行为随着其状态变化而变化,可以使用状态模式让每个状态行为封装在不同的类中


状态模式包含以下几个主要角色:

1)上下文

负责维护当前的状态,并将客户端请求委托给当前状态对象

2)状态

定义了一个接口,用于封装与上下文相关的一个状态的行为

3)具体状态

实现状态接口的具体状态类,每个状态类封装了与该状态相关的行为


通过以下这个电梯控制系统来理解状态模式

1)定义状态接口

定义电梯的状态行为

复制代码
// 抽象状态
class ElevatorState {
    handle() {
      throw new Error('handle方法必须在具体状态类中实现')
    }
}

2)定义具体状态类

电梯有正在运行、停止、故障三个状态

复制代码
// 电梯正在运行状态
class RunningState extends ElevatorState {
    handle() {
      console.log('电梯正在运行...')
    }
  }
// 电梯停止状态
class StoppedState extends ElevatorState {
    handle() {
      console.log('电梯已停止...')
    }
}
// 电梯故障状态
class BrokenState extends ElevatorState {
    handle() {
      console.log('电梯出现故障,请维修...')
    }
}

3)定义电梯

负责维护当前状态

复制代码
// 电梯类
class Elevator {
    constructor() {
      this.state = null
    }
    // 设置电梯状态
    setState(state) {
      this.state = state
    }
    // 请求电梯执行对应的状态行为
    request() {
      this.state.handle()
    }
}

4)客户端

复制代码
function clientCode() {
    const elevator = new Elevator()
  
    // 电梯处于运行状态
    elevator.setState(new RunningState())
    elevator.request()  // 电梯正在运行...
  
    // 电梯停止
    elevator.setState(new StoppedState())
    elevator.request()  // 电梯已停止...
  
    // 电梯故障
    elevator.setState(new BrokenState())
    elevator.request()  // 电梯出现故障,请维修...
}

// 执行客户端代码
clientCode()

空对象模式

空对象模式用一个空对象替代 null 或空值对象 ,从而避免了在代码中出现null值的判断


它的使用场景如下:

1)避免空值判断:需要多次对对象进行null检查时

2)提供默认行为:如果对象不存在,可以使用空对象来提供默认行为,避免出现 null 的异常情况


空对象模式包含以下几个主要角色:

1)抽象对象

定义了对象的接口,空对象和正常对象都继承该接口

2)具体对象

继承了抽象对象接口,并实现了其具体的行为

3)空对象

实现了抽象对象接口,但所有方法都不会做任何事情,或者是做一些空的实现


通过以下这个购物车系统来理解空对象模式,Item对象表示购物车中的商品,如果某个商品不存在,传统的做法是检查该商品是否为null,而使用空对象模式时,我们可以用一个空的Item类来代替null,避免空检查

1)定义 Item 接口

表示购物车的商品

复制代码
// 抽象类
class Item {
  getPrice() {
    throw new Error('getPrice方法必须在子类中实现')
  }
}

2)定义具体商品类

表示购物车中的实际商品

复制代码
// 具体商品类
class RealItem extends Item {
    constructor(name, price) {
      super()
      this.name = name
      this.price = price
    }
    getPrice() {
      return this.price
    }
    getName() {
      return this.name
    }
}

3)定义空商品类

表示购物车中没有商品时的空对象

复制代码
// 空商品类
class NullItem extends Item {
    getPrice() {
      return 0  // 空商品的价格为0
    }
    getName() {
      return '无商品'  // 空商品返回"无商品"
    }
}

4)定义购物车类

管理购物车中的商品

复制代码
// 购物车类
class ShoppingCart {
    constructor() {
      this.items = []
    }
    // 添加商品到购物车
    addItem(item) {
      this.items.push(item)
    }
    // 获取购物车所有商品的总价格
    getTotalPrice() {
      return this.items.reduce((total, item) => total + item.getPrice(), 0)
    }
    // 获取购物车中所有商品的名称
    getItemsName() {
      return this.items.map(item => item.getName()).join(', ')
    }
}

5)客户端

复制代码
function clientCode() {
    const cart = new ShoppingCart()
  
    // 创建一个真实的商品
    const item1 = new RealItem('苹果', 5)
    cart.addItem(item1)
  
    // 创建一个空商品,表示购物车中没有其他商品
    const item2 = new NullItem()
    cart.addItem(item2)
  
    console.log(`购物车商品: ${cart.getItemsName()}`)  // 购物车商品: 苹果, 无商品
    console.log(`购物车总价: ${cart.getTotalPrice()}元`)  // 购物车总价: 5元
}

// 执行客户端代码
clientCode()

策略模式

策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码


它的使用场景如下:

1)当一个系统中有许多类,它们之间的区别仅在于它们的行为时


策略模式包含以下几个主要角色:

1)环境类

持有一个策略对象,并可以在运行时改变所使用的策略

2)策略接口

声明一个公共接口,不同的策略类都实现这个接口

3)具体策略类

实现策略接口,定义具体的算法


通过以下这个购物折扣策略来理解策略模式,电商平台需要根据不同的策略计算订单的折扣,不同的折扣策略包括:满减折扣、打折折扣和无折扣

1)定义策略接口

声明折扣计算的方法

复制代码
// 策略接口
class DiscountStrategy {
    calculate(price) {
      throw new Error('calculate方法必须在具体策略类中实现')
    }
}

2)定义具体的折扣策略

复制代码
// 满减折扣策略
class FullReductionDiscount extends DiscountStrategy {
    calculate(price) {
      if (price > 100) {
        return price - 20  // 满100减20
      }
      return price
    }
}
// 打折折扣策略
class PercentageDiscount extends DiscountStrategy {
    constructor(discount) {
      super()
      this.discount = discount  // 折扣比例
    }
    calculate(price) {
      return price * (1 - this.discount)  // 按照折扣比例计算折扣后价格
    }
}
// 无折扣策略
class NoDiscount extends DiscountStrategy {
    calculate(price) {
      return price  // 无折扣,价格不变
    }
}

3)定义上下文类

定义购物车类,持有折扣策略

复制代码
// 购物车类
class ShoppingCart {
    constructor() {
      this.items = []
      this.discountStrategy = new NoDiscount()  // 默认无折扣策略
    }
    // 添加商品到购物车
    addItem(item) {
      this.items.push(item)
    }
    // 设置折扣策略
    setDiscountStrategy(strategy) {
      this.discountStrategy = strategy
    }
    // 计算总价格
    calculateTotalPrice() {
      let total = this.items.reduce((sum, item) => sum + item.price, 0)
      return this.discountStrategy.calculate(total)  // 使用策略计算折扣后的总价
    }
}

4)客户端

复制代码
function clientCode() {
    const cart = new ShoppingCart()
  
    // 添加商品到购物车
    cart.addItem({ name: '商品1', price: 50 })
    cart.addItem({ name: '商品2', price: 80 })
  
    // 设置满减折扣策略
    cart.setDiscountStrategy(new FullReductionDiscount())
    console.log(`总价(满减策略): ${cart.calculateTotalPrice()}元`)  // 满100减20
  
    // 设置打折折扣策略
    cart.setDiscountStrategy(new PercentageDiscount(0.1))  // 10%折扣
    console.log(`总价(打折策略): ${cart.calculateTotalPrice()}元`)  // 10%折扣
  
    // 设置无折扣策略
    cart.setDiscountStrategy(new NoDiscount());
    console.log(`总价(无折扣策略): ${cart.calculateTotalPrice()}元`)  // 无折扣
  }

// 执行客户端代码
clientCode()

模板模式

模板模式定义了一个算法的框架,将一些步骤延迟到子类中。通过模板方法,子类可以重定义算法的某些特定步骤而无需改变算法的结构

模板模式通常用于一些固定的算法步骤,其中某些步骤是可以被子类实现的,而有些步骤是固定不变的


它的使用场景如下:

1)当一个算法的整体结构是固定的,但某些步骤的实现可能会有所不同

2)当有多个子类共享相同的算法框架时,可以通过模板方法将共同的部分抽取到父类中


模板模式包含以下几个主要角色:

1)抽象类

定义了一个模板方法,它包含了一些固定的算法步骤,并且将某些步骤定义为抽象方法,交由子类实现

2)具体类

实现了抽象类中定义的抽象方法,从而完成具体的算法步骤


通过以下制作咖啡和茶来理解模板模式,制作这两种饮品的流程类似,但其中某些步骤不同

1)定义抽象类

定义制作饮品的模板方法,模板方法定义了制作饮品的步骤

复制代码
// 抽象类
class Drink {
    // 模板方法
    make() {
      this.boilWater()
      this.brew()
      this.pourInCup()
      this.addCondiments()
    }
    // 固定的步骤
    boilWater() {
      console.log("烧开水")
    }
    pourInCup() {
      console.log("倒入杯中")
    }
    // 可变的步骤,由子类实现
    brew() {
      throw new Error("抽象方法brew()必须在子类中实现")
    }
    addCondiments() {
      throw new Error("抽象方法addCondiments()必须在子类中实现")
    }
}

2)定义具体类(咖啡和茶)

复制代码
// 具体类:制作咖啡
class Coffee extends Drink {
    brew() {
      console.log("冲泡咖啡")
    }
    addCondiments() {
      console.log("加入糖和牛奶")
    }
}
// 具体类:制作茶
class Tea extends Drink {
    brew() {
      console.log("泡茶")
    }
    addCondiments() {
      console.log("加入柠檬")
    }
}

3)客户端

复制代码
function clientCode() {
    const coffee = new Coffee()
    console.log("制作咖啡:")
    coffee.make()  // 按照模板步骤制作咖啡
  
    console.log("\n制作茶:")
    const tea = new Tea()
    tea.make()  // 按照模板步骤制作茶
}
clientCode()

执行代码,运行结果如下:

访问者模式

访问者模式允许在元素结构内部定义一个操作,并且将该操作应用于不同类型的元素,而不需要改变元素的类本身


它的使用场景如下:

1)当需要对一个对象结构中的对象执行多种不同的且不相关的操作时,尤其是这些操作需要避免"污染"对象类本身


访问者模式包含以下几个主要角色:

1)访问者

定义访问元素的接口

2)具体访问者

实现访问者接口,提供对每个具体元素类的访问和相应操作

3)元素

定义一个接受访问者的方法

4)具体元素

实现元素接口,提供一个accept方法,允许访问者访问并操作

5)对象结构

定义了如何组装具体元素,如一个组合类


通过以下这个员工薪资处理来理解访问者模式,计算不同员工的薪资,在不改变员工类的情况下,增加不同类型的薪资计算方法

1)定义访问者接口

定义了对不同员工的操作

复制代码
// 访问者接口
class IVisitor {
    visitManager(manager) {
      throw new Error("必须实现 visitManager")
    }
    visitDeveloper(developer) {
      throw new Error("必须实现 visitDeveloper")
    }
    visitDesigner(designer) {
      throw new Error("必须实现 visitDesigner")
    }
}

2)定义元素接口和具体元素

复制代码
// 员工接口:定义接受访问者的方法
class Employee {
    accept(visitor) {
      throw new Error("必须实现 accept 方法")
    }
  }
// 经理类:具体员工类型
class Manager extends Employee {
    constructor(name, salary) {
      super()
      this.name = name
      this.salary = salary
    }
    accept(visitor) {
      visitor.visitManager(this)
    }
}
// 程序员类:具体员工类型
class Developer extends Employee {
    constructor(name, salary) {
      super()
      this.name = name
      this.salary = salary
    }
    accept(visitor) {
      visitor.visitDeveloper(this)
    }
}
// 设计师类:具体员工类型
class Designer extends Employee {
    constructor(name, salary) {
      super()
      this.name = name
      this.salary = salary
    }
    accept(visitor) {
      visitor.visitDesigner(this)
    }
}

3)定义具体访问者

复制代码
// 具体访问者:薪资计算
class SalaryVisitor extends IVisitor {
    visitManager(manager) {
      console.log(`经理 ${manager.name} 的薪水是 ${manager.salary + 1000} 元`)
    }
    visitDeveloper(developer) {
      console.log(`程序员 ${developer.name} 的薪水是 ${developer.salary + 500} 元`)
    }
    visitDesigner(designer) {
      console.log(`设计师 ${designer.name} 的薪水是 ${designer.salary + 700} 元`)
    }
}

4)客户端

复制代码
function clientCode() {
    // 创建不同的员工
    const manager = new Manager("John", 5000)
    const developer = new Developer("Alice", 4000)
    const designer = new Designer("Bob", 3000)

    // 创建薪资计算访问者
    const salaryVisitor = new SalaryVisitor()
  
    // 员工接受访问者,进行薪资计算
    manager.accept(salaryVisitor)
    developer.accept(salaryVisitor)
    designer.accept(salaryVisitor)
}
clientCode()

执行代码,运行结果如下:

相关推荐
无双_Joney15 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥17 分钟前
前端必学的 CSS Grid 布局体系
前端·css
ccnocare18 分钟前
选择文件夹路径
前端
艾小码18 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月19 分钟前
JavaScript作用域与作用域链详解
前端·面试
数据智能老司机20 分钟前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机20 分钟前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机21 分钟前
精通 Python 设计模式——性能模式
python·设计模式·架构
泉城老铁23 分钟前
idea 优化卡顿
前端·后端·敏捷开发