当前位置: 首页 > article >正文

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

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

责任链模式

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


它的使用场景如下:

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()

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

在这里插入图片描述


http://www.kler.cn/a/516595.html

相关文章:

  • Linux 执行 fdisk -l 出现 GPT PMBR 大小不符 解决方法
  • Gin 学习笔记
  • (回溯分割)leetcode93 复原IP地址
  • SecureUtil.aes数据加密工具类
  • 计算机网络 (57)改进“尽最大努力交付”的服务
  • 工作~酒场指南
  • Recaptcha2 图像怎么识别
  • Linux pgrep 命令详解
  • vben5 admin ant design vue如何使用时间范围组件RangePicker
  • kotlin内联函数——takeIf和takeUnless
  • java读取设置pdf属性信息
  • 二分查找题目:快照数组
  • Docker Hub 全面解析及应对策略
  • 2【选修】再探宝可梦、数码宝贝分类器
  • 组播IGMP协议报文介绍
  • QT6 + CMAKE编译OPENCV3.9
  • 1.23寒假作业
  • linux中关闭服务的开机自启动
  • “上门按摩” 小程序开发项目:基于 SOP 的全流程管理
  • C语言文件操作:标准库与系统调用实践
  • 【Linux】其他备选高级IO模型
  • IPhone16 Plus 设备详情
  • 详解:TCP/IP五层(四层)协议模型
  • 23.日常算法
  • CVPR 2024 无人机/遥感/卫星图像方向总汇(航空图像和交叉视角定位)
  • pandas基础:文件的读取和写入