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

news2024/10/21 13:19:59

一. 前言

在前端开发中,命令模式(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.coloradmin.cn/o/2213892.html

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

相关文章

实战子网掩码划分问题

拓扑要求总部有2个业务段,分部也有3个业务 还有互联地址段,还有管理地址段!!!! 23117 个段 192.168.1.0/24 怎么划分呢? 1 根据要求划分地址段 192.168.1.0/24 要划分7个网段那就…

linux------缓冲区与C库的原理

前言 一、缓冲区 缓冲区的作用是提高效率,因为将数据写入到设备,是需要调用系统接口的,如果每次写入缓冲区的数据就调用一次系统调用,涉及到系统调用这时操作系统就会介入,用户态转为内核态,这个过程需要时…

自然语言到 SQL 的曙光:我们准备好了吗?

发布于:2024 年 10 月 08 日 各位读者,国庆假期已过,我们打工人要开启奋斗新征程了,今天小编也是刚上班假期综合征还没过去,就被抓过来读论文,还好我在假期没闲着,整理了几篇关于 NL2SQL 的最新…

周易解读:两仪01

两 仪01 上一节里面,我们讲解了太极的知识。关于太极呢,它是可以有着多方面的含义。在这里呢,我们主要地,选取它的宇宙生成中的一种含义,认为太极是有所萌动,有所积累,但是呢,就是…

【YOLOv11】实战一:在LabVIEW 中使用OpenVINO实现YOLOv11

‍‍🏡博客主页: virobotics(仪酷智能):LabVIEW深度学习、人工智能博主 🎄所属专栏:『LabVIEW深度学习实战』 📑推荐文章:『LabVIEW人工智能深度学习指南』 🍻本文由virobotics(仪酷…

单体应用、SOA 和微服务架构的区别

在软件架构的发展历程中,单体应用、面向服务架构(SOA)和微服务架构都有着各自的特点和适用场景。了解它们之间的区别,有助于我们在不同的项目需求下做出更合适的架构选择。 一、单体应用 (一)定义与特点 …

DAY8 Final等

Final关键字 final修饰静态变量,这个变量今后被称为常量, 可以记住一个固定值,并且程序中不能修改了,通常这个值作为系统的配置信息。常量的名称,建议全部大写,多个单词用下划线连接。 public static final…

PaddleOCR的包装生产日期识别_随记1

一、前言 项目难点 没有训练数据图像质量层次不齐: 角度倾斜、图片模糊、光照不足、过曝等问题严重 二.、环境搭建 本任务基于Aistudio完成, 具体环境如下: 操作系统: LinuxPaddleOCR: PaddleOCR_2.7text_renderer: masterpaddlepaddle-gpu2.6.0cuda版本是11.8,…

量化投资学习

1:投资定义就是付出一定的代价,期望能够得到一定汇报,可能会出现没有回报 2:投资分析流派 2.1:宏观策略分析法:从宏观经济大方向入手,再应用到具体股票也叫自上而下的研究方法,需要理解这个趋势的核心驱动…

TS中的接口、泛型、自定义类型 与vue3的使用

泛型的使用场景: 当我们定义了一个对象时,在多次传值的过程中,其对象中的属性值被更改了,以至于后期拿不到我们需要的数据。 举个例子,当我们使用person.name时,是可以使用的。但是多次传递之后&#xff…

讯方·智汇云校华为ICT大赛赛前辅导直播安排

华为ICT大赛赛前辅导直播安排 网络赛道在“智汇云校”视频号上观看。 直播时间: 网络:2024.10.14-10.15-10.17-10.18-10.21-10.23-10.25-10.28-10.29-10.30-11.1-11.4-11.5-11.6,每晚19:30-22:00 安全:2024…

【Next.js 入门教程系列】05-数据库

原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的库点个star,关注一下吧 上一篇【Next.js 入门教程系列】04-构造 API 数据库 本篇包括以下内容: Setting up PrismaDefining data modelsCreat…

pinia学习笔记(1.0)

首先贴出官网地址:开始 | Pinia pinia作为Vue3项目中常用的状态管理工具,正逐渐取代vuex,现从0到1自己搭建pinia仓库。 首先,安装pinia,使用包管理器工具(npm,pnpm,yarn,Bun等都可以) 安装成…

QGIS如何在正射影像中获得绘制线段的长度信息||QGIS如何获取绘制点的经纬度(如何生成WKT属性)并且导出为CSV文件

QGIS如何在正射影像中获得绘制线段的长度信息 前置条件: 导入正射影像和绘制完需要计算长度的绘制线段后 1.检查菜单栏是否有数据处理(processing)按键,如果没有的话请根据以下链接打开processing工具箱:https://bl…

微服务架构 --- Nacos的项目实战操作

目录 一.什么是Nacos? 二.什么是注册中心? 1.注册中心的定义: 2.为什么需要使用注册中心? 3.注册中心原理: 三.Nacos的使用: 1.安装与启动Nacos: 2.集成 Nacos 服务注册与发现&#xff…

邀你一起共建谷歌封号申诉共享库

大家好,我是牢鹅!相信很多老粉是通过谷歌封号的三篇文章《聊聊「谷歌8.3」账号封禁解析与应对思路 》和《聊聊谷歌「高风险」封号问题解析与应对思路》、《聊聊这半年来,谷歌为何频繁封号?》关注牢鹅的,在这将近半年的…

架构设计笔记-13-层次式架构设计理论与实践

目录 知识要点 综合知识 案例分析 1.SSM框架 2.MVC模式,XML 3.MVC架构脆弱性 4.容器技术的优势 5.对象关系映射ORM技术 知识要点 一般来说,架构可以分为表现层、中间层和持久层三个层次。 表现层:表现层主要负责接收用户的请求&…

【论文翻译】TITAN:用于交通流量预测的异构专家混合模型

题目A TIME SERIES IS WORTH FIVE EXPERTS: HETEROGENEOUS MIXTURE OF EXPERTS FOR TRAFFIC FLOW PREDICTION论文链接https://arxiv.org/pdf/2409.17440源码地址https://github.com/sqlcow/TITAN(作者说论文被接受后,代码将更新) 摘要 准确的…

API项目4:开发SDK

面临另一个问题 作为开发者,每次调用接口都需要自己生成时间戳,编写签名算法,生成随机数等等,这些都是相当繁琐的工作。 因此,要想办法让开发者能够以最简单的方式调用接口。开发者只需要关心传递哪些参数以及他们的…

如何通过计算巢在阿里云一键部署FlowiseAI

什么是FlowiseAI FlowiseAI 是一个开源的低代码开发工具,专为开发者构建定制的语言学习模型(LLM)应用而设计。 通过其拖放式界面,用户可以轻松创建和管理AI驱动的交互式应用,如聊天机器人和数据分析工具。 它基于Lang…