上篇我们介绍了 设计模式之结构型模式篇,接下来介绍设计模式之行为型模式篇
责任链模式
责任链模式允许将请求沿着一条链传递,直到有一个对象处理它为止。每个处理者都有机会处理该请求,或者将其传递给链中的下一个处理者,每个处理者只关心自己能处理的请求,而不关心请求的来源和链中的其他处理者
它的使用场景如下:
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)定义具体观察类
定义WeatherApp
、WeatherWebsite
两个具体的观察类,显示天气数据
// 具体的观察者类
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()
执行代码,运行结果如下: