深入理解JavaScript设计模式之命令模式

深入理解JavaScript设计模式之命令模式

深入理解JavaScript设计模式之命令模式

文章目录

  • 深入理解JavaScript设计模式之命令模式
    • 定义
    • 简单命令模式
    • 组合命令模式
    • 使用命令模式实现文本编辑器
      • 目标
      • 关键类说明
      • 实现的效果
      • 交互逻辑流程
      • 所有代码:
    • 总结

定义

命令模式也是设计模式种相对于变焦简单容易理解的一种设计模式。

JavaScript中,命令模式用于将一个请求或简单操作封装为一个对象。这使得你可以使用不同的请求、队列请求或者记录请求日志、撤销操作等。命令模式通常用于实现诸如撤销/重做功能、事务系统以及在复杂对象间传递请求等场景。

白话说就是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。使得请求发送者和请求接收者能够消除彼此之间的耦合关系,命令模式还支持撤销、排队等操作。

简单命令模式

定义很难了解命令模式的用处,举个开灯关灯的命令模式例子,如下,定义了两个LightOnCommand LightOffCommand 两个命令分别执行light 对象中的onoff方法,在new LightOnCommand的时候将light作为参数传入并执行LightOnCommand.execute();的方法实现开灯关灯的操作。

<body><button id="btn">按钮</button>
</body>
<script>/*** 点击按钮,执行开灯关灯的操作*/const light = {on() {console.log("开灯");},off() {console.log("关灯");},};class LightOnCommand {constructor(light) {this.light = light;}execute() {this.light.on();}}class LightOffCommand {constructor(light) {this.light = light;}execute() {this.light.off();}}const onLight = new LightOnCommand(light);const offLight = new LightOffCommand(light);let isOn = true;document.getElementById("btn").addEventListener("click", function () {(isOn ? onLight : offLight).execute();isOn = !isOn;});
</script>

深入理解JavaScript设计模式之命令模式
一开始学的时候觉得这就是脱裤子放屁多此一举,但是仔细还差与思考,使用这种方式,可以让代码更加模块化与更容易维护。

  • 解耦:调用者和接收者之间解耦,调用者不需要知道接收者的具体实现。
  • 扩展性:可以很容易地添加新的命令而不需要修改现有的类。
  • 可撤销操作:可以通过记录命令的历史来实现撤销操作。
  • 队列请求:可以将命令存储在队列中,按顺序执行。
  • 日志记录:可以记录命令的历史,便于调试和回溯。

组合命令模式

第一个简单的例子可以看到点击按钮只执行了一次命令,如果有多条命令,那就可以将多个命令添加到Command 里的一个stack 数组中,最后执行Command.execute的时候遍历stack数组中的命令统一遍历执行。
页面中有一个按钮 #btn,当点击按钮时,依次执行以下三个命令:

  1. 开灯(LightOnCommand)
  2. 工人开始工作并停止(WorkerCommand)
  3. 关灯(LightOffCommand)

这些命令被添加到一个 Command 对象中,并在点击事件发生时统一执行,代码如下:

<body><button id="btn">按钮</button>
</body>
<script>class Command {constructor() {this.stack = [];}add(command) {this.stack.push(command);}execute() {this.stack.forEach((command) => command.execute());}}const light = {on: () => console.log("开灯"),off: () => console.log("关灯"),};const worker = {do: () => console.log("开始工作"),stop: () => console.log("停止工作"),};class WorkerCommand {constructor(worker) {this.worker = worker;}execute() {this.worker.do();this.worker.stop();}}// 命令拆分class LightOnCommand {constructor(light) {this.light = light;}execute() {this.light.on();}}class LightOffCommand {constructor(light) {this.light = light;}execute() {this.light.off();}}const command = new Command();command.add(new LightOnCommand(light));command.add(new WorkerCommand(worker));command.add(new LightOffCommand(light));document.getElementById("btn").addEventListener("click", () => {command.execute();});
</script>

深入理解JavaScript设计模式之命令模式
这种写法的优点:

  1. 解耦调用者与执行者 按钮点击事件(调用者)并不直接调用 light.on()worker.do(),而是交给命令对象去处理。 这样使得界面逻辑和业务逻辑分离,提高了可维护性。
  2. 易于扩展新的命令 如果需要新增功能,比如“打开风扇”或“播放音乐”,只需要定义一个新的命令类并加入命令队列即可,不需要修改已有代码。 符合 开放封闭原则(OCP):对扩展开放,对修改关闭。
  3. 支持组合命令 Command类中的 stack 可以保存多个命令,可以轻松实现宏命令(一组命令的集合),如示例中的一键执行开灯、工作、关灯等操作。 后续也可以支持撤销/重做等功能(只需记录历史栈)。
  4. 便于测试与复用 每个命令是独立的对象,可以单独测试其 execute() 方法。 命令可以在不同上下文中复用,例如在定时器中触发、远程调用等。
  5. 提升代码可读性和结构清晰度 将每个操作抽象为类,有助于理解意图(Intent)。 比如看到 new LightOnCommand(light),就知道这是“开灯”的命令,比直接调用函数更具语义化。

总的来说:通过组合命令模式可以实现良好的职责分离,灵活扩展和统一控制,如果需求遇到了对多个操作进行封装调度记录和撤销的时候,可以使用组合命令实现。

使用命令模式实现文本编辑器

如下举例加深命令模式的使用,如下我想实现一个文本编辑器,其中功能有【清空内容转为大写转为小写撤销重做指令列表
深入理解JavaScript设计模式之命令模式

目标

实现一个基于命令模式的文本编辑器,具备【清空内容转为大写转为小写撤销重做指令列表显示每一步操作的命令记录

关键类说明

  • Editor(接收者):
class Editor {constructor() {this.content = "";}
}

存储当前文本内容,所用命令的实际执行者。

  • TextChangeCommand(基础命令)
class TextChangeCommand {constructor(editor, newText) {this.editor = editor;this.newText = newText;this.previousText = editor.content;}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}
}

表示每次文本输入变更的操作,记录修改前后的状态,支持撤销。

  • CommandManager(扩展命令)
class CommandManager {constructor() {this.tack = [];}execute(command) {if (command) {this.tack.push(command);command.execute();updateUI();}}// 清空redo() {this.tack = [];updateUI();}// 撤销undo() {if (this.tack.length > 0) {const command = this.tack.pop();command.undo();updateUI();} else {console.log("没有可撤销的命令");updateUI();return;}}// 查看命令列表getTackList() {return this.tack;}}

使用栈(tack)保存所有已执行命令,提供 execute()undo()redo()getTackList() 方法,控制整个命令流程。

  • UpperCaseCommand(命令管理器)
class UpperCaseCommand {
constructor(editor) {this.editor = editor;this.previousText = editor.content;this.newText = editor.content.toUpperCase();}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}
}

将文本转为大写的命令,同样支持撤销,可以继续扩展更多命令如 LowerCaseCommand,ClearCommand 等。

实现的效果

深入理解JavaScript设计模式之命令模式

交互逻辑流程

  1. 初始化
    创建 EditorCommandManager,设置初始文本为空,绑定 DOM 元素(如 textarea、按钮)。
  2. 用户操作触发命令,输入文字 → 触发input事件 → 创建TextChangeCommand → 执行并入栈
    点击按钮(清空、大写、小写)→ 创建对应命令 → 执行并入栈。
  3. 撤销 / 重做,“撤销”点击 → 从栈中弹出最后一个命令 → 调用 .undo(),“重做”点击 → 清空栈(当前简单实现) 当前重做只是清空栈,没有真正实现“恢复撤销”的动作,可进一步改进。

所有代码:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>命令模式文本编辑器</title><style>body {margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;background: linear-gradient(45deg, #ff6b6b, #c471ad);font-family: Arial, sans-serif;}.editor-container {width: 300px;background: white;border-radius: 5px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);}.header {background: #2c3e50;color: white;text-align: center;padding: 10px 0;border-top-left-radius: 5px;border-top-right-radius: 5px;}.content {padding: 20px;height: 150px;border-bottom: 1px solid #ddd;}.buttons {display: flex;justify-content: space-around;padding: 10px;}.buttons button {padding: 8px 15px;border: none;border-radius: 3px;cursor: pointer;}.buttons .primary {background: #3498db;color: white;}.buttons .secondary {background: #ecf0f1;color: #333;}#contentText {border: none;height: 150px;width: 100%;}#tackListView {border: none;height: 130px;width: 100%;}</style></head><body><div class="editor-container"><div class="header">命令模式文本编辑器</div><div class="content"><!-- 文本编辑区域 --><textarea id="contentText"></textarea></div><div class="buttons"><button class="primary" id="clearBtn">清空内容</button><button class="primary" id="upperBtn">转为大写</button><button class="primary" id="lowerBtn">转为小写</button></div><div class="buttons"><button class="secondary" id="undoBtn">撤销</button><button class="secondary" id="redoBtn">重做</button><button class="secondary" id="stackList">指令列表</button></div><div class="content">命令列表:<textarea id="tackListView"></textarea></div></div><script>class TextChangeCommand {constructor(editor, newText) {this.editor = editor;this.newText = newText;this.previousText = editor.content;}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}}class CommandManager {constructor() {this.tack = [];}execute(command) {if (command) {this.tack.push(command);command.execute();updateUI();}}// 清空redo() {this.tack = [];updateUI();}// 撤销undo() {if (this.tack.length > 0) {const command = this.tack.pop();command.undo();updateUI();} else {console.log("没有可撤销的命令");updateUI();return;}}// 查看命令列表getTackList() {return this.tack;}}class UpperCaseCommand {constructor(editor) {this.editor = editor;this.previousText = editor.content;this.newText = editor.content.toUpperCase();}execute() {this.editor.content = this.newText;}undo() {this.editor.content = this.previousText;}}// 接收者class Editor {constructor() {this.content = "";}}// 初始化const editor = new Editor();const commandManager = new CommandManager();// DOM元素const textarea = document.getElementById("contentText");// 设置初始内容editor.content = textarea.value;// 事件监听textarea.addEventListener("input", function () {const command = new TextChangeCommand(editor, textarea.value);commandManager.execute(command);});document.getElementById("clearBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor, "");commandManager.execute(command);});document.getElementById("upperBtn").addEventListener("click", function () {const command = new UpperCaseCommand(editor);commandManager.execute(command);});document.getElementById("lowerBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor,textarea.value.toLowerCase());commandManager.execute(command);});document.getElementById("undoBtn").addEventListener("click", function () {commandManager.undo();});document.getElementById("redoBtn").addEventListener("click", function () {const command = new TextChangeCommand(editor, "");commandManager.execute(command);commandManager.redo();});document.getElementById("stackList").addEventListener("click", function () {console.log(commandManager.getTackList());});// 更新UIfunction updateUI() {// 更新主文本区域textarea.value = editor.content;// 获取命令列表显示区域const tackListView = document.getElementById("tackListView");// 获取当前命令栈const commands = commandManager.getTackList();// 格式化命令记录let logText = "";for (let i = 0; i < commands.length; i++) {const cmd = commands[i];if (cmd instanceof TextChangeCommand) {logText += `${i + 1}. 文本修改为: ${cmd.newText}\n`;} else if (cmd instanceof UpperCaseCommand) {logText += `${i + 1}. 转为大写: ${cmd.newText}\n`;}}// 如果没有命令,显示提示信息if (commands.length === 0) {logText = "暂无命令记录";}// 更新命令列表显示区域tackListView.value = logText;}// 初始化UI更新updateUI();</script></body>
</html>

总结

设计模式不是“炫技”,而是"沉淀",希望通过阅读和学习《JavaScript设计模式》和实践中,在显示业务需求开发中写出更具有可维护性,可扩展性的代码。

致敬—— 《JavaScript设计模式》· 曾探

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87912.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87912.shtml

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

相关文章

CSS 网页布局:从基础到进阶

CSS 网页布局&#xff1a;从基础到进阶 引言 随着互联网的飞速发展&#xff0c;网页设计已经成为了一个不可或缺的领域。CSS&#xff08;层叠样式表&#xff09;作为网页设计中的关键工具&#xff0c;用于控制网页元素的样式和布局。本文将为您全面解析CSS网页布局&#xff0c;…

【人工智能】大语言模型(LLM) NLP

大语言模型&#xff08;LLM&#xff09;& NLP1.大语言模型&#xff08;LLM&#xff09;1.1 一句话解释1.2 更形象的比喻1.3 为什么叫 “大” 模型1.4 它能做什么1.5 现实中的例子2.对比 NLP2.1 用 “汽车进化” 比喻 NLP → LLM2.2 为什么说 LLM 属于 NLP2.3 LLM 的 “革命…

Unity HDRP + Azure IoT 的 Python 后端实现与集成方案

Unity HDRP Azure IoT 的 Python 后端实现与集成方案 虽然Unity HDRP本身使用C#开发&#xff0c;但我们可以构建Python后端服务支持物联网系统&#xff0c;并与Unity引擎深度集成。以下是完整的实现方案&#xff1a; 系统架构 #mermaid-svg-qCDb0g9Ik287Cg8X {font-family:&qu…

小黑黑日常积累大模型prompt句式2:【以段落的形式输出,不分点列举】【如果没有相关内容则不输出】【可读性强】【输出格式规范】

以段落的形式输出&#xff0c;不分点列举 每个标题下直接接续段落内容&#xff0c;不编号、不分点。......标题下直接接续段落内容&#xff0c;不继续进行分点列举。如果没有相关内容则不输出 若某一部分无法从原文中提取有效信息&#xff0c;则跳过该部分内容&#xff0c;不做…

React Native 基础组件详解<一>

一、Text组件 1&#xff09;numberOfLines&#xff1a;显示行数 2&#xff09;ellipsizeMode&#xff1a;超出隐藏的位置 clip->裁掉 head/middle/ tail->点的位置 3&#xff09;selectable: 是否可以选中 4&#xff09;selectionColor&#xff1a;选中后的颜色 5&#…

异步编程(Promise/Generator/async)

1、Promise 2、Generator 3、async/await

【Note】《Kafka: The Definitive Guide》 第8章: Cross-Cluster Data Mirroring

《Kafka: The Definitive Guide》 第8章&#xff1a; Cross-Cluster Data Mirroring 一、跨集群镜像的场景与价值 多区域低延迟访问 将业务数据从主集群实时复制到多个地理区域的集群&#xff0c;缩短消费者跨区读取延迟。 灾备切换 当主集群出现故障时&#xff0c;可快速将消…

「Windows/Mac OS」AIGC图片生成视频 ,webui + stable-diffusion环境部署教程

stable-diffusion webui 环境搭建目录 一、Windows 环境部署 stable-diffusion-webui1、准备条件2、安装Python 3.10.X&#xff08;**较新版本的 Python 不支持 torch**&#xff09;3、安装Git 教程4、使用Git 下载 stable-diffusion-webui 存储库&#xff0c;4.1、显示报错 5…

【深度学习】 深度学习训练配置参数详解

深度学习训练配置参数详解 1. 启动初始化参数说明CUDA_VISIBLE_DEVICES指定使用的GPU设备编号&#xff08;"0"表示单卡&#xff09;seed随机种子&#xff08;1777777&#xff09;&#xff0c;保证实验可复现性cuda是否启用GPU加速&#xff08;True&#xff09;benchm…

期望,积分,均值,求和的关系

1. 回顾期望的定义 对于连续性随机变量 X X X&#xff0c;期望为&#xff1a; E X ∼ f ( x ) [ X ] ∫ Ω x f ( x ) d x E_{X\sim f(x)}[X] \int_{\Omega}xf(x)dx EX∼f(x)​[X]∫Ω​xf(x)dx 其中 f ( x ) f(x) f(x)为概率密度函数&#xff0c; Ω \Omega Ω为概率密度函…

1.如何对多个控件进行高效的绑定 C#例子 WPF例子

使用ObservableCollection高效为多个控件绑定数据在WPF开发中&#xff0c;数据绑定是一个非常重要的功能&#xff0c;它允许我们将UI控件与数据源进行绑定&#xff0c;从而实现数据的自动更新。当需要为多个控件绑定数据时&#xff0c;使用ObservableCollection可以大大提高开发…

JSONLines和JSON数据格式使用教程

文章目录 一、核心区别二、JSONLines 的优势三、Python 中使用 JSONLines1. 写入 JSONLines 文件2. 读取 JSONLines 文件3. 处理大文件示例四、常见工具支持1. 命令行工具2. 编程语言库五、适用场景选择六、注意事项总结JSONLines(简称 jsonl 或 jl)和传统 JSON 都是用于存储…

链表算法之【反转链表】

目录 LeetCode-206题 LeetCode-206题 给定一个单链表的头节点&#xff0c;请反转链表&#xff0c;并返回反转后的链表 class Solution {public ListNode reverseList(ListNode head) {// checkif (head null || head.next null)return head;// 双指针ListNode p1 head;Li…

回溯题解——子集【LeetCode】输入的视角(选或不选)

78. 子集 ✅ 一、算法逻辑讲解&#xff08;逐步思路&#xff09; 逻辑讲解&#xff1a; dfs(i)&#xff1a;表示从下标 i 开始&#xff0c;做“选 or 不选”的子集构造。 终止条件 if i n&#xff1a; 到达数组末尾&#xff0c;表示一种完整子集构造完成。 把当前构造路径…

使用Electron开发跨平台本地文件管理器:从入门到实践

在当今数字化时代&#xff0c;文件管理是每个计算机用户日常工作中不可或缺的一部分。虽然操作系统都提供了自己的文件管理器&#xff0c;但开发一个自定义的文件管理器可以带来更好的用户体验、特定功能的集成以及跨平台的一致性。本文将详细介绍如何使用Electron框架构建一个…

JBHI 2025 | 潜在扩散模型赋能胸部X射线骨抑制

Abstract: 肺部疾病是全球健康面临的一项重大挑战&#xff0c;胸部 X 光检查&#xff08;CXR&#xff09;因其方便性和经济性而成为一种重要的诊断工具。 然而&#xff0c;CXR 图像中重叠的骨结构往往会阻碍肺部病变的检测&#xff0c;从而导致潜在的误诊。 为解决这一问题&am…

408第三季part2 - 计算机网络 - 计算机网络基本概念

理解然后区分一下这2个区别特点是建立连接存储转发的意思是A先发给B&#xff0c;B再发给C&#xff0c;就这样这里缺点比如A很大&#xff0c;你给B缓存开销大还需要排序然后形象的图题目分组头部要放一些源地址和目的地址这些东西以后发数据只会往近的发&#xff0c;不可能往下面…

互补功率放大器Multisim电路仿真——硬件工程师笔记

目录 1 互补功率放大器基础知识 1.1 工作原理 1.2 电路结构 1.3 优点 1.4 缺点 1.5 应用 1.6 总结 2 OCL乙类互补功率放大电路 2.1 电路结构 2.2 工作原理 2.3 优点 2.4 缺点 2.5 总结 3 OCL甲乙类互补功率放大电路 3.1 电路结构 3.2 工作原理 3.3 优点 3.4 …

【1】确认安装 Node.js 和 npm版本号

搭建前端项目时需要安装 Node.js 和 npm&#xff0c;主要是因为它们提供了一些重要的功能和工具&#xff0c;帮助开发者高效地开发、构建和管理项目。一、具体原因如下&#xff1a; Node.js&#xff1a;JavaScript 运行环境 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运…

7、从网络中获取数据

目录 订阅网络状态变化创建网络对象获取默认激活网络及其能力可订阅事件可订阅事件——网络可用事件可订阅事件——网络阻塞状态事件可订阅事件——网络能力变化事件可订阅事件——网络连接信息变化事件可订阅事件——网络丢失事件常见事件订阅场景 开发流程 使用HTTP访问网络发…