JavaScript组合模式

news2024/11/19 4:47:37

JavaScript组合模式

  • 1 什么是组合模式
  • 2 宏命令
  • 3 示例:扫描文件夹
  • 4 引用父对象

1 什么是组合模式

组合模式是一种结构型设计模式,用于将对象组合成树形结构,并使客户端能够统一处理单个对象和组合对象。它通过使用继承和组合两个概念,允许我们以递归方式构建对象树。

在组合模式中,有两种类型的对象:基本对象组合对象。基本对象是单独的、不可拆分的对象,而组合对象是由多个基本对象组合而成的对象。

组合模式将对象组合成树形结构,以表示“部分—整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

2 宏命令

宏命令对象包含了一组具体的子命令对象,不管是宏命令对象,还是子命令对象,都有一个execute方法负责执行命令,例如家里有一个万能遥控器,每天回家的时候,只要按一个特别的按钮,它就会帮我们关上房间门,顺便打开电脑并登录游戏。

var closeDoorCommand = {
  execute: function () {
    console.log("关门");
  },
};
var openPcCommand = {
  execute: function () {
    console.log("开电脑");
  },
};
var openGameCommand = {
  execute: function () {
    console.log("打开游戏");
  },
};

var MacroCommand = function () {
  return {
    commandsList: [],
    add: function (command) {
      this.commandsList.push(command);
    },
    execute: function () {
      for (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute();
      }
    },
  };
};

var macroCommand = MacroCommand();

macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();

在上面这段代码中,我们发现,宏命令中包含了一组子命令,它们组成了一个如下所示的树形结构。
在这里插入图片描述
其中,marcoCommand被称为组合对象,closeDoorCommandopenPcCommandopenGameCommand都是叶对象。在macroCommandexecute方法里,并不执行真正的操作,而是遍历它所包含的叶对象,把真正的execute请求委托给这些叶对象。

macroCommand表现得像一个命令,但它实际上只是一组真正命令的“代理”。并非真正的代理,虽然结构上相似,但macroCommand只负责传递请求给叶对象,它的目的不在于控制对叶对象的访问。

如果我们需要一个“超级万能遥控器”,可以控制家里所有的电器,这个遥控器拥有以下功能:

  • 打开空调
  • 打开电视和音响
  • 关门、开电脑、登录游戏

首先在节点中放置一个按钮button来表示这个超级万能遥控器,超级万能遥控器上安装了一个宏命令,当执行这个宏命令时,会依次遍历执行它所包含的子命令,代码如下:

<button id="button">点击一下</button>
<script>
  // -------- 绑定超级命令 -----------
  var button = document.getElementById("button");
  button.onclick = function () {
    macroCommand.execute();
  };
</script>
var MacroCommand = function () {
  return {
    commandsList: [],
    add: function (command) {
      this.commandsList.push(command);
    },
    execute: function () {
      for (var i = 0, command; (command = this.commandsList[i++]); ) {
        command.execute();
      }
    },
  };
};

var openAcCommand = {
  execute: function () {
    console.log("打开空调");
  },
};

// -------- 打开电视和打开音响命令组合 -----------
var openTvCommand = {
  execute: function () {
    console.log("打开电视");
  },
};

var openSoundCommand = {
  execute: function () {
    console.log("打开音响");
  },
};

var macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);

// -------- 关门、打开电脑和打开游戏命令组合 -----------
var closeDoorCommand = {
  execute: function () {
    console.log("关门");
  },
};
var openPcCommand = {
  execute: function () {
    console.log("开电脑");
  },
};

var openGameCommand = {
  execute: function () {
    console.log("打开游戏");
  },
};

var macroCommand2 = MacroCommand();
macroCommand2.add(closeDoorCommand);
macroCommand2.add(openPcCommand);
macroCommand2.add(openGameCommand);

// -------- 组合超级命令 -----------
var macroCommand = new MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);

在这里插入图片描述
从这个例子中可以看到,基本对象可以被组合成更复杂的组合对象,组合对象又可以被组合,这样不断递归下去,这棵树的结构可以支持任意多的复杂度。在树最终被构造完成之后,让整颗树最终运转起来的步骤非常简单,只需要调用最上层对象的execute方法。每当对最上层的对象进行一次请求时,实际上是在对整个树进行深度优先的搜索,而创建组合对象的人并不关心这些内在的细节,往这棵树里面添加一些新的节点对象是非常容易的事情。

3 示例:扫描文件夹

文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树。

首先分别定义好文件夹Folder和文件File这两个类。见如下代码:

/******************************* Folder ******************************/
var Folder = function (name) {
  this.name = name;
  this.files = [];
};

Folder.prototype.add = function (file) {
  this.files.push(file);
};

Folder.prototype.scan = function () {
  console.log("开始扫描文件夹:" + this.name);
  for (let i = 0, file, files = this.files; (file = files[i++]); ) {
    file.scan();
  }
};

/******************************* File ******************************/
var File = function (name) {
  this.name = name;
};

File.prototype.add = function () {
  throw new Error("文件下面不能再添加文件");
};

File.prototype.scan = function () {
  console.log("开始扫描文件: " + this.name);
};

接下来创建一些文件夹和文件对象, 并且让它们组合成一棵树,这棵树就是我们D盘里的现有文件目录结构:

var folder = new Folder("学习资料");
var folder1 = new Folder("JavaScript");
var folder2 = new Folder("jQuery");
var file1 = new File("JavaScript 设计模式与开发实践");
var file2 = new File("精通 jQuery");
var file3 = new File("重构与模式");
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);

在这里插入图片描述
现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设我们已经得到了这些文件对象:

var folder3 = new Folder("Nodejs");
var file4 = new File("深入浅出 Node.js");
folder3.add(file4);
var file5 = new File("JavaScript 语言精髓与编程实践");

接下来就是把这些文件都添加到原有的树中:

folder.add(folder3);
folder.add(file5);

在这里插入图片描述
运用了组合模式之后,扫描整个文件夹的操作也是轻而易举的,我们只需要操作树的最顶端对象:

folder.scan();

在这里插入图片描述

4 引用父对象

组合对象保存了它下面的子节点的引用,这是组合模式的特点,此时树结构是从上至下的。但有时候我们需要在子节点上保持对父节点的引用,比如在组合模式中使用职责链时,有可能需要让请求从子节点往父节点上冒泡传递。还有当我们删除某个文件的时候,实际上是从这个文件所在的上层文件夹中删除该文件的。

现在来改写扫描文件夹的代码,使得在扫描整个文件夹之前,我们可以先移除某一个具体的文件。

首先改写Folder类和File类,在这两个类的构造函数中,增加this.parent属性,并且在调用add方法的时候,正确设置文件或者文件夹的父节点:

/******************************* Folder ******************************/
var Folder = function (name) {
  this.name = name;
  this.files = [];
  this.parent = null;
};

Folder.prototype.add = function (file) {
  file.parent = this; // 设置父对象
  this.files.push(file);
};

Folder.prototype.scan = function () {
  console.log("开始扫描文件夹:" + this.name);
  for (let i = 0, file, files = this.files; (file = files[i++]); ) {
    file.scan();
  }
};

接下来增加Folder.prototype.remove方法,表示移除该文件夹:

Folder.prototype.remove = function () {
  if (!this.parent) return;
  for (let files = this.parent.files, l = files.length - 1; l >= 0; l--) {
    let file = files[l];
    if (file === this) {
      files.splice(l, 1);
    }
  }
};

File类的实现基本一致:

/******************************* File ******************************/
var File = function (name) {
  this.name = name;
  this.parent = null;
};

File.prototype.add = function () {
  throw new Error("文件下面不能再添加文件");
};

File.prototype.scan = function () {
  console.log("开始扫描文件: " + this.name);
};

File.prototype.remove = function () {
  if (!this.parent) return;
  for (let files = this.parent.files, l = files.length - 1; l >= 0; l--) {
    let file = files[l];
    if (file === this) {
      files.splice(l, 1);
    }
  }
};

下面测试一下我们的移除文件功能:

var folder = new Folder("学习资料");
var folder1 = new Folder("JavaScript");
var file1 = new File("深入浅出 Node.js");
var file2 = new File("JavaScript 设计模式与开发实践");
folder1.add(file2);
folder.add(folder1);
folder.add(file1);

folder1.remove(); // 移除文件夹
folder.scan();

在这里插入图片描述

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

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

相关文章

题目描述:输入数字,第一行为数组的大小,第二行为数组的值。求其中相邻两个数字相差不大于8的最大片段的长度。

题目描述&#xff1a; 输入数字&#xff0c;第一行为数组的大小&#xff0c;第二行为数组的值。求其中相邻两个数字相差不大于8的最大片段的长度。 示例1&#xff1a; 输入&#xff1a;91 2 4 6 12 2 8 6 4 输出&#xff1a;5示例2&#xff1a; 输入&#xff1a;101 4 5 6 2…

数据清洗(1)--数据查缺补漏

前言 之前使用scikit 进行一些基础模型的选择&#xff08;SVM支持向量机&#xff0c;LR算法&#xff0c;KNN&#xff0c;SGD&#xff0c;Bays贝叶斯&#xff0c;决策树&#xff0c;随机森林&#xff09;&#xff0c;创建&#xff0c;训练&#xff08;测试集验证集&#xff09;…

Redis测试新手入门教程

在测试过程中&#xff0c;我们或多或少会接触到Redis&#xff0c;今天就把在小破站看到的三丰老师课程&#xff0c;把笔记整理了下&#xff0c;用来备忘&#xff0c;也希望能给大家带来亿点点收获。 主要分为两个部分&#xff1a; 一、缓存技术在后端架构中是如何应用的&#…

C语言实现求某班学生的平均成绩和均方差

完整代码&#xff1a; /* 已知求成绩的平均值和均方差公式为&#xff1a;&#x1d44e;&#x1d463;&#x1d452; ∑&#x1d460;&#x1d456;/n , &#x1d451;&#x1d452;&#x1d463; √(∑ (&#x1d460;&#x1d456;−&#x1d44e;&#x1d463;&#x1d45…

Android开发知识学习——HTTPS

文章目录 定义HTTPS连接HTTPS 连接建立的过程课后题 定义 HTTP Secure / HTTP over SSL / HTTP over TLS SSL&#xff1a;Secure Socket Layer -> TLS Transport Layer Security 定义&#xff1a;在HTTP之下增加的一个安全层&#xff0c;用于保障HTTP的加密传输 本质&…

Python 自动化测试全攻略:五种自动化测试模型实战详解!

随着移动互联网的发展&#xff0c;软件研发模型逐步完善&#xff0c;软件交付质量越来越受到软件公司的重视&#xff0c;软件测试技术特别是自动化测试技术开始在软件系统研发过程中发挥着越来越重要的作用。 与传统的手工测试技术相比&#xff0c;自动化测试具备了良好的可操…

Mysql进阶-索引篇(下)

SQL性能分析 SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次&#xff0c;通过sql语句的访问频次&#xff0c;我们可…

技术视角下的跑腿小程序开发:关键挑战和解决方案

跑腿小程序作为连接服务提供者和用户的桥梁&#xff0c;面临着诸多技术挑战。本文将聚焦于技术层面的关键挑战&#xff0c;并提供解决方案&#xff0c;以帮助开发者应对技术上的复杂问题。 1. 实时性与性能挑战 挑战&#xff1a; 跑腿小程序需要实时地匹配订单、更新状态和提…

【java学习—十】操作集合的工具类Collections(8)

文章目录 1. 操作集合的工具类&#xff1a; Collections2. 应用3. 查找、替换3.1. max 与 min3.2. 根据Comparator返回max(min) 3.3. frequency 与 replaceAll4. 同步控制 1. 操作集合的工具类&#xff1a; Collections Collections 是一个操作 Set 、List 和 Map 等集合的工具…

从LLM到AIGC,开启AI时代寻宝之旅!

引言 还记得《头号玩家》中男主人公赢得游戏的方式吗——在游戏中漫无目的地闲逛&#xff0c;然后发现彩蛋&#xff0c;获得胜利。 在人工智能领域&#xff0c;有一种新奇性搜索原则的实验&#xff0c;使得机器研究的成功之路恰似寻找彩蛋的道路。比如在不设定“出门”目标的情…

为什么会被【禅道】工具的公司踢出QQ群的反思…………

周末备份Gitlab的代码库&#xff0c;把Gitlab更新到了最新的16.5。顺带看了禅道官网出了最新版本18.8。但是禅道的升级更新并不顺利…………。 先说一下为什么用禅道这个工具&#xff1a; 再使用禅道这个工具前&#xff0c;使用过的工具有QC(Quality Center)、jira&#xff0…

测试工程师简历编写攻略:打造高点击率的简历!

简历是入职职场的一张名片&#xff0c;也是进入职场一块“敲门砖”。从某种角度说&#xff0c;简历也是一张专业人员的说明书。 软件测试人员作为IT行业具有技术含量的职业&#xff0c;一份优秀的简历包含的内容以及如何写好简历尤为重要。接下来从以下两方面来介绍这个话题&a…

Android任务栈和启动模式

Andrcid中的任务栈是一种用来存放Activity实倒的容器。任务最大的特点就是先进后出&#xff0c;它主要有两个基本操作&#xff0c;分别是压栈和出栈。通常Andaid应用程序都有一个任务栈&#xff0c;每打开一个Activity时&#xff0c;该Activity就会被压入任务栈。每销毁一个Act…

eclipse Occurrence

eclipse Occurrence Occurrence of initUi2_setData_99 Window->Preferences->General->Editors->Text Editors->Annotations->Occurrences 个人感觉最好用的颜色&#xff1b; 边线&#xff0c;正文都可以看得清楚

Elasticsearch(一)---介绍

简介 Elasticsearch是一个基于Lucene的实际的分布式搜索和分析引擎。设计用于云计算中&#xff0c;能够达到近实时搜索&#xff0c;稳定&#xff0c;可靠&#xff0c;快速&#xff0c;安装使用方便。基于RESTful接口。 官网地址&#xff1a;Elasticsearch 平台 — 大规模查找…

开关电源测试方案分享:电源纹波及噪声测试方法、测试标准

纹波及噪声影响着设备的性能和稳定性&#xff0c;是开关电源测试的重要环节。通过电源纹波噪声测试&#xff0c;检测电源纹波情况&#xff0c;从而提升开关电源的性能。纳米软件开关电源自动化测试软件助力纹波和噪声测试&#xff0c;提升测试效能。 开关电源纹波及噪声测试方法…

一文分享提升嵌入式代码的分析工具

当前标准的C语言编译器存在普遍只能找出代码中潜在的缺陷&#xff0c;而对程序方案设计并没有效。 使用静态代码分析器有助于提升固件和捕获编译器难以察觉的问题。 1、用于嵌入式的常见代码静态分析工具 代码静态分析工具&#xff0c;顾名思义就是对代码进行静待分析&#…

【Python图像处理局部放大】输入图像,选择两处不同的区域进行放大操作,然后将原始图像、处理后的两个区域以及标记合成新图像进行展示

NeRF-w 渲染后的图像效果的定性对比&#xff0c;对局部放大以观察细节效果。如下 对单张渲染后的图像&#xff0c;选择指定区域并进行放大操作&#xff0c;然后将原始图像、处理后的两个区域以及标记连线的新图像&#xff0c;如下图 代码 from PIL import Image, ImageDraw# 读…

预制件二维码怎么做

凡尔码的二维码管理系统不仅提供了高效便捷的二维码生成功能&#xff0c;还配备了一套完善的预制构件管理方案。通过扫描二维码&#xff0c;用户可以查看预制构件的详细信息&#xff0c;包括项目名称、构建名称、安装位置、重量、生产日期、生产单位、产品出厂检验材料、尺寸等…

《2023整车操作系统发展趋势研究》丨附下载

✦ ✦✦ ✦✦ ✦✦ ✦ 1996年&#xff0c;Gartner提出SOA其核心思想在于“通过将庞大的计算系统按照实际业务拆分为独立部署的大小合适的功能模块&#xff0c;提高功能单元的复用性&#xff0c;降低产品开发的复杂度和成本”。 如今&#xff0c;软件定义汽车领域引入SOA&…