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

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

责任链模式

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


它的使用场景如下:

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

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

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


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

1)抽象处理者

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

2)具体处理者

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

3)客户端

向链上的处理者发出请求


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

1)定义请求类

包含请求的内容

// 定义请求类
class Request {constructor(amount, description) {this.amount = amountthis.description = description}
}

2)定义抽象处理者

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

// 定义抽象处理者类
class Approver {constructor(name) {this.name = namethis.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 = leftthis.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 = bookShelfthis.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 = namethis.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 = datathis.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 = namethis.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 = namethis.salary = salary}accept(visitor) {visitor.visitManager(this)}
}
// 程序员类:具体员工类型
class Developer extends Employee {constructor(name, salary) {super()this.name = namethis.salary = salary}accept(visitor) {visitor.visitDeveloper(this)}
}
// 设计师类:具体员工类型
class Designer extends Employee {constructor(name, salary) {super()this.name = namethis.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.mzph.cn/news/893573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[2025分类时序异常检测指标R-AUC与VUS]

梳理了一下分类中常见的指标&#xff0c;这些指标与时序异常检测中新提出的A-RUC与VUS之间的关系 真正例(True Positive,TP): 被正确识别为正样本的数量。真负例(True Negative,TN): 被正确识别为负样本的数量。假正例(False Positive ,FP): 被错误识为正样本数量假负例(Fals…

Unity中实现伤害跳字效果(简单好抄)

第一步骤安装并导入Dotween插件&#xff08;也可以不用导入之后直接下载我的安装包&#xff09; 官网DOTween - 下载 第二步&#xff1a; 制作跳字预制体 建议把最佳适应打开&#xff0c;这样就不怕数字太大显示不全了。 第三步&#xff1a;创建一个空对象并编写脚本JumpNumbe…

【设计模式-行为型】观察者模式

一、什么是观察者模式 说起观察者模式&#xff0c;不得不说一位观察者模式的高级应用者&#xff0c;朱元璋。不知道大家有没有看过胡军演的电视剧《朱元璋》。这部剧背景是元朝末年&#xff0c;天下大乱&#xff0c;朱元璋自幼父母双亡&#xff0c;沦为乞丐&#xff0c;后遁入空…

考研机试:学分绩点

描述 北京大学对本科生的成绩施行平均学分绩点制&#xff08;GPA&#xff09;。 既将学生的实际考分根据不同的学科的不同学分按一定的公式进行计算。 公式如下&#xff1a; 一门课程的学分绩点 该课绩点 该课学分 总评绩点 所有学科学分绩点之和 / 所有课程学分之和 …

CentOS9 安装Docker+Dpanel+onlyoffice(https、更改字体、字号、去除限制)的避坑笔记

CentOS9 安装Dockeronlyoffice&#xff08;https、更改字体、字号、去除文件大小限制&#xff09;的避坑笔记 一、安装Docker二、更新docker镜像源&#xff1a;三、安装Dpanel四、安装onlyoffice&#xff08;开启https及一些碰到的问题&#xff09;五、更改字体和字号六、去除限…

【玩转全栈】----YOLO8训练自己的模型并应用

继上篇&#xff1a; 【玩转全栈】---基于YOLO8的图片、视频目标检测-CSDN博客 相信大家已经可以训练一些图片和视频了&#xff0c;接下来我将为大家介绍如何训练自己的特定模型&#xff0c;并用其进行检测 目录 准备数据 图片数据 标识数据 配置文件 运行 测试训练结果 存在的问…

OpenCV文字绘制支持中文显示

OpenCV版本&#xff1a;4.4 IDE&#xff1a;VS2019 功能描述 OpenCV绘制文本的函数putText()不支持中文的显示&#xff0c;网上很多方法推荐的都是使用FreeType来支持&#xff0c;FreeType是什么呢&#xff1f;FreeType的官网上有介绍 FreeType官网 https://www.freetype.or…

梯度下降法 (Gradient Descent) 算法详解及案例分析

梯度下降法 (Gradient Descent) 算法详解及案例分析 目录 梯度下降法 (Gradient Descent) 算法详解及案例分析1. 引言2. 梯度下降法 (Gradient Descent) 算法原理2.1 基本概念2.2 算法步骤2.3 梯度下降法的变种3. 梯度下降法的优势与局限性3.1 优势3.2 局限性4. 案例分析4.1 案…

GPSd定时检测保活TCP GPS源

为了在 TCP GPS 源丢失连接时自动重新连接&#xff0c;可以编写一个监控脚本&#xff0c;定期检查 gpspipe 输出中的 TCP 源数据是否存在。如果检测到丢失&#xff0c;则使用 gpsdctl 或直接命令重新添加 TCP 源。 1、工具 检查并安装必要工具&#xff0c;本例需要使用 gpspi…

我谈《概率论与数理统计》的知识体系

学习《概率论与数理统计》二十多年后&#xff0c;在廖老师的指导下&#xff0c;才厘清了各章之间的关系。首先&#xff0c;这是两个学科综合的一门课程&#xff0c;这一门课程中还有术语冲突的问题。这一门课程一条线两个分支&#xff0c;脉络很清晰。 概率论与统计学 概率论…

ElasticSearch JavaRestClient查询之快速入门

文章目录 查询操作流程概述构建并发起请求1. 创建请求对象2. 设置请求体3. 发送请求 查询结果的解析1. 解析结果结构2. 获取总条数3. 获取命中的数据 完整示例代码总结 查询操作流程概述 Elasticsearch 查询操作大致可以分为两个部分&#xff1a; 构建并发起请求&#xff1a;…

Quartus:开发使用及 Tips 总结

Quartus是Altera&#xff08;现已被Intel收购&#xff09;推出的一款针对其FPGA产品的综合性开发环境&#xff0c;用于设计、仿真和调试数字电路。以下是使用Quartus的一些总结和技巧(Tips)&#xff0c;帮助更高效地进行FPGA项目开发&#xff1a; 这里写目录标题 使用总结TIPS…

elementUI Table组件实现表头吸顶效果

需求描述 当 table 内容过多的时候&#xff0c;页面上滑滚动&#xff0c;表头的信息也会随着被遮挡&#xff0c;无法将表头信息和表格内容对应起来&#xff0c;需要进行表头吸顶 开始编码&#x1f4aa; 环境&#xff1a;vue2.6、element UI step1&#xff1a; 给el-table__h…

用于牙科的多任务视频增强

Multi-task Video Enhancement for Dental Interventions 2022 miccai Abstract 微型照相机牢牢地固定在牙科手机上&#xff0c;这样牙医就可以持续地监测保守牙科手术的进展情况。但视频辅助牙科干预中的视频增强减轻了低光、噪音、模糊和相机握手等降低视觉舒适度的问题。…

Vue3轮播图左右联动

1、轮播图部分&#xff0c;右边鼠标移入&#xff0c;左边对应展示轮播图 可以在swiper 官网 Swiper中文网-轮播图幻灯片js插件,H5页面前端开发 选择vue中使用swiper npm i swiper 左右两边的联动&#xff1a;左边的轮播图和右边的小的列表他们的列表组成结构是一样的&#…

windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】

注意版本依赖【本实验版本如下】 Hadoop 3.1.1 spark 2.3.2 scala 2.11 1.依赖环境 1.1 java 安装java并配置环境变量【如果未安装搜索其他教程】 环境验证如下&#xff1a; C:\Users\wangning>java -version java version "1.8.0_261" Java(TM) SE Runti…

go-zero框架基本配置和错误码封装

文章目录 加载配置信息配置 env加载.env文件配置servicecontext 查询数据生成model文件执行查询操作 错误码封装配置拦截器错误码封装 接上一篇&#xff1a;《go-zero框架快速入门》 加载配置信息 配置 env 在项目根目录下新增 .env 文件&#xff0c;可以配置当前读取哪个环…

2025 最新flutter面试总结

目录 1.Dart是值传递还是引用传递&#xff1f; 2.Flutter 是单引擎还是双引擎 3. StatelessWidget 和 StatefulWidget 在 Flutter 中有什么区别&#xff1f; 4.简述Dart语音特性 5. Navigator 是什么&#xff1f;在 Flutter 中 Routes 是什么&#xff1f; 6、Dart 是不是…

HarmonyOS Next构建工具 lycium 原理介绍

HarmonyOS Next构建工具 lycium 原理介绍 背景介绍 HarmonyOS Next中很多系统API是以C接口提供&#xff0c;如果要使用C接口&#xff0c;必须要使用NAPI在ArkTS与C间交互&#xff0c;这种场景在使用DevEco-Studio中集成的交叉编译工具&#xff0c;以及cmake构建工具就完全够用…

最长递增——蓝桥杯

1.题目描述 在数列 a1​,a2​,⋯,an​ 中&#xff0c;如果ai​<ai1​<ai2​<⋯<aj​&#xff0c;则称 ai​ 至 aj​ 为一段递增序列&#xff0c;长度为 j−i1。 定一个数列&#xff0c;请问数列中最长的递增序列有多长。 输入描述 输入的第一行包含一个整数 n。…