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

一. 前言

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

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

相关文章

知识图谱入门——7:阶段案例:使用 Protégé、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop 搭建知识图谱

在 Windows 环境中结合使用 Protg、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop,可以高效地实现从自然语言处理(NLP)到知识图谱构建的全过程。本案例将详细论述环境配置、步骤实现以及一些扩展和不足之处。 源文件已上传我的资源区。 文章…

使用 Docker 部署前端项目:Vue 和 React 结合 Nginx 实现静态文件托管

使用 Docker 部署前端项目:Vue 和 React 结合 Nginx 实现静态文件托管 Web 开发中,将前端项目(例如 Vue 或 React 应用)打包后通过 Docker 容器和 Nginx 部署是非常常见的方式。它不仅简化了部署流程,还能确保在不同环…

4G路由网关R10在智能制造生产线的应用

在当今智能制造的时代,高效稳定的网络连接和数据传输至关重要。4G 路由网关 R10 以其卓越的性能,在智能制造生产线中发挥着重要作用。 4G 路由网关 R10 是一款功能强大的网络设备。它支持多种网络连接方式,包括 4G 网络、有线网络等&#xff…

MySQL连接:内连接

先看我的表结构 dept表 emp表 内连接分为两个连接方式 1.隐式内连接 2.显式内连接 1.隐式内连接 基本语法 select 字段列表 FROM 表1, 表2 WHERE 条件... ;例子:查询每一个员工的姓名,及关联的部门的名称(隐式内连接实现) …

【C++ STL算法】二分查找 lower_bound、upper_bound、equal_range、binary_search

文章目录 【 1. 首个不小于 lower_bound 】【 2. 首个大于 upper_bound 】【 3. 所有等于 equel_range 】【 4. 二分查找 binary_search 】 当 指定区域内的数据处于有序状态 时,如果想查找某个目标元素,更推荐使用二分查找的方法(相比顺序查…

电影选票选座系统|影院购票|电影院订票选座小程序|基于微信小程序的电影院购票系统设计与实现(源码+数据库+文档)

电影院订票选座小程序 目录 基于微信小程序的电影院购票系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户功能实现 2、管理员功能实现 (1)影院信息管理 (2)电影信息管理 (3)已完成…

Internet Download Manager6.42免费版下载神器新体验

🚀 开篇就燃!你的下载速度被“TA”承包了 #### 🌟 初识IDM 6.42,下载界的“超跑”驾到 各位追求效率的小伙伴们,今天小红要来揭秘一款让我彻底告别“龟速”下载的神器——Internet Download Manager (简称IDM) 6.42版&…

xtu oj 四位数

样例输入# 2 1990 1111样例输出# 5 0 分离整数与合并 AC代码 #include<stdio.h> //判断四个数码是否相等 int Judge(int n){int flag1;int gn%10,sn/10%10,bn/100%10,qn/1000;if(gs&&gb&&gq)flag0;return flag; } int main(){int T;scanf("%d…

dayu_widgets-简介

前言: 越来越多的人开始使用python来做GUI程序&#xff0c;市面上却很少有好的UI控件。即使有也是走的商业收费协议&#xff0c;不敢使用&#xff0c;一个不小心就收到法律传票。 一、原始开源项目: 偶然在GitHub上发现了这个博主的开源项目。https://github.com/phenom-films…

抽象类Abstart Class

抽象类其实就是一种不完全的设计图 必须用abstract修饰 模板方法&#xff1a;建议使用final修饰&#xff0c;不能被重写。

DGL库之HGTConv的使用

DGL库之HGTConv的使用 论文地址和异构图构建教程HGTConv语法格式HGTConv的使用 论文地址和异构图构建教程 论文地址&#xff1a;https://arxiv.org/pdf/2003.01332 异构图构建教程&#xff1a;异构图构建 异构图转同构图&#xff1a;异构图转同构图 HGTConv语法格式 dgl.nn.…

AI智能聊天问答系统源码+AI绘画系统+图文搭建部署教程,文生图图生图,TTS语音识别输入,AI智能体,文档分析

一、前言 人工智能的快速进步吸引了全球的瞩目&#xff0c;各式AI应用如绘图、语言模型和视频处理等已在多个领域获得应用。这些技术不仅加速了科技的创新&#xff0c;也在艺术创作、内容生产和商业实践等方面显示了其巨大潜力。例如&#xff0c;AI语言模型极大提升了内容自动…

【动态规划-最长公共子序列(LCS)】【hard】【科大讯飞笔试最后一题】力扣115. 不同的子序列

给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数&#xff0c;结果需要对 10^9 7 取模。 示例 1&#xff1a; 输入&#xff1a;s “rabbbit”, t “rabbit” 输出&#xff1a;3 解释&#xff1a; 如下所示, 有 3 种可以从 s 中得到 “rabbit”…

ABAP 表转JSON格式

FUNCTION ZRFC_FI_SEND_PAYPLAN2BPM. *"---------------------------------------------------------------------- *"*"本地接口&#xff1a; *" IMPORTING *" VALUE(INPUT) TYPE ZSRFC_FI_SEND_PAYBPM_IN *" EXPORTING *" VAL…

vue3数字滚动插件vue3-count-to

1.安装 npm i vue3-count-to 2.引入 import { CountTo } from vue3-count-to3.使用 <countTo :startVal"0" :endVal"57.63" :decimals"2" :duration"3000"></countTo> 配置项:

yolov5-7.0模型DNN加载函数及参数详解(重要)

yolov5-7.0模型DNN加载函数及参数详解&#xff08;重要&#xff09; 引言yolov5&#xff08;v7.0&#xff09;1&#xff0c;yolov5.h(加载对应模型里面的相关参数要更改)2&#xff0c;main主程序&#xff08;1&#xff09;加载网络&#xff08;2&#xff09;检测推理&#xff0…

AVL树如何维持平衡

1.AVL树的特性 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种…

【万字长文】Word2Vec计算详解(一)CBOW模型

【万字长文】Word2Vec计算详解&#xff08;一&#xff09;CBOW模型 写在前面 本文用于记录本人学习NLP过程中&#xff0c;学习Word2Vec部分时的详细过程&#xff0c;本文与本人写的其他文章一样&#xff0c;旨在给出Word2Vec模型中的详细计算过程&#xff0c;包括每个模块的计…

【redis-06】redis的stream流实现消息中间件

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…

Auto-Animate:是一款零配置、即插即用的动画工具,可以为您的 Web 应用添加流畅的过渡效果

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 用户体验成为了检验产品成功与否的关键因素。而动画效果&#xff0c;作为提升用户体验的重要手段&#xff0c;在网页和应用开发中扮演着举足轻重的角色…