JavaScript组合模式-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

JavaScript组合模式

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();

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: JavaScriptJava