JavaScript 命令模式实战:打造可撤销的操作命令

devtools/2024/10/11 1:47:45/

一. 前言

前端开发中,命令模式(Command Pattern)作为一种行为型设计模式,可以帮助我们将请求封装成一个对象,从而实现调用对象和执行对象之间的解耦,方便扩展和修改。

本文将和大家分享 JavaScript 中的命令模式,包括命令模式的定义、核心角色,以及在 JavaScript 中如何实现命令模式

二. 什么是命令模式

1. 定义

命令模式是一种行为型设计模式,它可以将请求封装成一个对象,从而使调用操作的对象和执行操作的对象解耦。命令模式的核心思想是将请求封装成一个对象,从而使命令的发起者和执行者分离。

2. 核心角色

命令模式中,通常包括以下几个核心角色:

  • 命令接口(Command):定义了执行命令的方法,通常包括一个 execute 方法。

  • 具体命令(Concrete Command):实现了命令接口,负责具体的命令执行。

  • 调用者(Invoker):负责调用命令对象执行请求的对象。

  • 接收者(Receiver):执行命令的请求操作。

命令模式通过将请求封装成一个对象,实现了命令的发起者和执行者的解耦,同时也支持命令的排队、记录、撤销等操作。

3. UML

image.png

三. 实现方式

在 JavaScript 中,可以通过对象和函数的组合来实现命令模式。以下是一种简单的实现方式:

1. 定义命令接口

首先,定义一个命令接口,通常包括一个 execute 方法,用于执行命令。

// Command 接口
class Command {execute() {// 空方法,具体命令会实现自己的 execute 方法}
}

2. 定义具体命令

接下来,定义具体的命令,实现 Command 接口,并在 execute 方法中实现具体的命令逻辑。

// 具体命令
class ConcreteCommand extends Command {constructor(receiver) {super();this.receiver = receiver;}execute() {this.receiver.action();}
}

3. 定义接收者

接收者负责执行实际的命令操作。

// 接收者
class Receiver {action() {console.log("接收者执行命令");}
}

4. 定义调用者

调用者负责调用具体的命令对象,并执行命令。

// 调用者
class Invoker {constructor(command) {this.command = command;}executeCommand() {this.command.execute();}
}

5. 使用命令模式

最后,可以创建具体的命令对象、接收者对象和调用者对象,并使用命令模式来执行命令。

// 创建接收者对象
const receiver = new Receiver();// 创建具体命令对象并传入接收者
const concreteCommand = new ConcreteCommand(receiver);// 创建调用者并传入具体命令对象
const invoker = new Invoker(concreteCommand);// 调用者执行命令
invoker.executeCommand(); // 输出:接收者执行命令

通过上述方式,我们实现了一个简单的命令模式示例。通过这种方式,可以实现命令的封装、调用者和接收者的解耦,以及支持命令的撤销和重做等操作。在实际应用中,我们还可以根据具体业务场景来设计和扩展命令模式,以提高代码的灵活性。

四. 简单的播放器应用

image.png

下面是一个我在实际项目中使用命令模式的应用场景实例分析:

我们有一个简单的播放器应用,用户可以通过界面上的按钮来控制音乐的播放、暂停、上一首、下一首等操作。因此我选择使用命令模式来实现这个播放器应用,具体步骤如下:

1. 创建命令对象

class PlayCommand {constructor(player) {this.player = player;}execute() {this.player.play();}
}class PauseCommand {constructor(player) {.player = player;}execute() {this.player.pause();}
}// 其他命令对象类似实现...

2. 创建接收者对象(播放器对象)

class Player {play() {console.log("播放音乐");}pause() {console.log("暂停音乐");}// 其他控制音乐的方法...
}

3. 创建调用者对象(按钮对象)

class Button {constructor(command) {this.command = command;}onClick() {this.command.execute();}
}// 创建按钮和命令对象的对应关系
const player = new Player();
const playCommand = new PlayCommand(player);
const pauseCommand = new PauseCommand(player);const playButton = new Button(playCommand);
const pauseButton = new Button(pauseCommand);// 用户点击按钮时触发对应的命令
playButton.onClick(); // 输出:播放音乐
pauseButton.onClick(); // 输出:暂停音乐

在上面的示例中,通过使用命令模式,我们实现了按钮与播放器之间的解耦,按钮只需要知道对应的命令对象,而不需要知道具体执行的逻辑。这样可以方便地扩展新的命令,并且可以更好地管理用户操作。

五. Canvas 绘图命令实现绘图与撤销

命令模式前端开发中还有许多其他的应用场景,特别是在撤销操作等方面,通过把命令封装成对象,可以把不同的命令管理起来,带来更加灵活和可控的操作方式。

在 Web 绘图中,Canvas 是我们经常打交道的,Canvas 拥有非常多的 API,因此我们在使用中遗忘是不可避免的,所以使用命令模式可以将不同的绘制图形 API 封装成不同的命令对象,可以实现和具体操作之间的解耦。

Canvas绘图.gif

Canvas绘图.gif

接下来我们来具体分析一下如何使用命令模式实现 Canvas 绘图与撤销的功能时,可以按照以下步骤进行:

1. 定义命令接口

首先定义一个命令接口,包括执行命令(execute)和撤销命令(undo)两个方法。

class Command {execute() {}undo() {}
}

2. 编写具体命令类

编写继承自命令接口的具体命令类,例如绘制矩形命令。

class DrawRectCommand extends Command {constructor(canvas) {super();this.canvas = canvas;}execute() {// 执行绘制矩形的操作}undo() {// 撤销绘制矩形的操作}
}class DrawCircleCommand extends Command {constructor(canvas) {super();this.canvas = canvas;}execute() {// 执行绘制圆形的操作}undo() {// 撤销绘制圆形的操作}
}

3. 创建命令队列

创建一个命令队列,用于存储执行的命令,以便实现撤销操作。

class CommandQueue {constructor() {this.commands = [];}addCommand(command) {this.commands.push(command);}executeCommands() {this.commands.forEach((command) => command.execute());}undoCommands() {this.commands.reverse().forEach((command) => command.undo());}
}

4. 实例化绘图功能

在 Canvas 绘图应用中,实例化绘图命令以及命令队列,并根据用户操作执行或者撤销命令。

const canvas = document.getElementById("canvas");
const drawRectangleCommand = new DrawRectangleCommand(canvas);
const drawCircleCommand = new DrawCircleCommand(canvas);
const commandQueue = new CommandQueue();// 用户执行绘图命令
commandQueue.addCommand(drawRectangleCommand);
commandQueue.executeCommands();commandQueue.addCommand(drawCircleCommand);
commandQueue.executeCommands();// 用户撤销操作
commandQueue.undoCommands();

通过以上步骤,可以利用命令模式来实现 Canvas 绘图与撤销的功能。当用户执行绘图操作时,将对应的命令存储在命令队列中,用户可以随时撤销之前的操作,实现了绘图与撤销的功能。同时你也可以根据业务需求继续完善更加复杂的绘图操作等。

码上掘金演示效果如下:

jcode

六. 优缺点

通过以上的了解和学习,我们能够清楚的知道命令模式作为一种设计模式,主要作用是将请求封装成一个对象,以便于解耦具体实现,我们调用者其实不关心实现,只关心具体的命令即可。那么它也有一些缺点,下面我来具体分析一下命令模式的优缺点:

1. 优点

  1. 解耦请求发送者和请求接收者命令模式可以将请求发送者和请求接收者解耦,发送者不需要知道接收者的具体实现,只需通过命令对象发送请求。

  2. 容易扩展新命令:由于命令模式将每个请求操作封装成一个对象,因此在需要新增新命令时,只需要新增一个相应的命令类,而无需修改现有的代码。

  3. 支持撤销和重做操作:通过记录命令历史,可以轻松实现请求的撤销和重做操作,增强了系统的灵活性。

  4. 支持命令队列:可以将命令对象保存在队列中,按照一定的顺序执行,实现批处理等功能。

  5. 易于实现日志和事务系统:通过记录执行的命令日志,可以实现日志记录和事务回滚等功能。

2. 缺点

  1. 可能会产生大量的具体命令类:如果系统中具有大量的命令操作且每个命令需要单独实现一个具体命令类,可能会导致类的数量过多,增加系统复杂度。

  2. 增加了系统的复杂度:引入命令模式会增加额外的类和对象,可能会使系统结构更加复杂,不适合简单的业务场景。

  3. 适用范围有限命令模式适用于需要请求发送者和接收者解耦、支持撤销重做等场景,对于简单的请求操作可能显得过于繁琐。

综上所述,命令模式在一些特定的场景下能够发挥其优势,如需支持撤销重做、命令队列等功能时,命令模式是一个不错的选择。但在简单的业务场景或对性能要求较高的场景下,可能并不适合采用命令模式。在实际应用中,需要根据具体情况来评估是否使用命令模式

七. 总结

命令模式是一种行为设计模式,它的核心思想是将请求封装成一个对象,从而使得调用者和接收者解耦。接收者是真正执行命令的对象,而调用者则只需要调用命令对象的方法,而不需要知道具体的实现细节。

实现命令模式的关键通常包括以下几个核心角色:

  • 命令接口(Command):定义了执行命令的方法,通常包括一个 execute 方法。

  • 具体命令(Concrete Command):实现了命令接口,负责具体的命令执行。

  • 调用者(Invoker):负责调用命令对象执行请求的对象。

  • 接收者(Receiver):执行命令的请求操作。

但是,命令模式也有一些缺点,如果我们的代码不规范可能会导致大量的具体命令类,增加系统复杂度,导致执行效率变低,因此在实际应用中,需要根据具体情况来评估是否使用命令模式


http://www.ppmy.cn/devtools/123926.html

相关文章

MyBatis 数据表与实体映射的隐藏陷阱

这两天在处理一个线上问题时,发现Mybatis数据表和实体映射的时候会埋一个坑。这个问题看似微小,但却可能在关键时刻给我们带来不小的困扰。接下来,让我们深入剖析这个问题,并探究其发生的根源。 一、问题描述 我们在使用 Mybati…

记录Android.bp里如何添加jar/aar文件

在项目libs目录下放入需要的jar/aar 文件,并新建Android.bp文件 android_library_import {name: "libaums_aar",aars: ["libaums-0.6.0.aar"],}java_import {name: "gson_jar",jars: ["gson-2.11.0.jar"]} 注意: aar 文件…

Ubuntu2404安装

Ubuntu是一款非常优秀的发行版本,起初她的优势主要在于桌面版,但是随着Centos 从服务版的支持的退出,Ubuntu server也在迅猛的成长,并且不断收获了用户,拥有了一大批忠实的粉丝。好了,废话不多说&#xff0…

pytest框架之fixture测试夹具详解

前言 大家下午好呀,今天呢来和大家唠唠pytest中的fixtures夹具的详解,废话就不多说了咱们直接进入主题哈。 一、fixture的优势 ​ pytest框架的fixture测试夹具就相当于unittest框架的setup、teardown,但相对之下它的功能更加强大和灵活。 …

android中byte[] buf没有结束符,new String(buf)会不会出错?

答案是:不会 看例子: 这和c是不一样的,不需要特别的在字符串后面添加一个\0结束.

游戏服务器防御策略:防止玩家因DDoS攻击而掉线

在网络游戏环境中,玩家体验至关重要。然而,DDoS(分布式拒绝服务)攻击是导致玩家在游戏中频繁掉线的一个重要原因。本文将探讨如何通过一系列技术和策略来减轻DDoS攻击的影响,保障玩家的游戏体验。 一、引言 DDoS攻击是…

Chrome(谷歌)浏览器 数据JSON格式美化 2024显示插件安装和使用

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 没有美化的格式浏览器展示 美化之后效果图 安装流程 下载地址 https://github.com/gildas-lormeau/JSONVue 点击下载 下载成功,如图所示 解压文件 添加成功,如图所示 通过浏览器…

OpenAI .NET 库稳定版发布,支持 GPT-4o 并改进 API 功能

penAI 在6月推出其官方 .NET 库的 beta 版之后,如今终于发布了稳定版。该库已在 NuGet 上作为包发布,支持最新的模型,如 GPT-4o 和 GPT-4o mini,并且提供完整的 OpenAI REST API。这次发布包括同步和异步 API,以及流式…