《javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解

下面是《Javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解的完整攻略。

下面是《Javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解的完整攻略。

什么是组合模式

组合模式是一种结构型设计模式,它通过将对象组合成树形结构来表示部分-整体的层次关系,使得用户对单个对象和组合对象的使用具有一致性。

换句话说,组合模式就是将对象组织成树形结构,以表示“部分-整体”的层次结构,并允许用户对单个对象和组合对象进行相同的操作,从而实现对整个树形结构的操作。

组合模式的结构

组合模式涉及到以下几种角色:

  1. 抽象类:组合中的对象声明接口,实现一些公共接口,同时也能定义一些默认行为或属性。
  2. 叶节点类:实现抽象节点所声明的接口,为组合中的叶节点对象提供公共接口。
  3. 组合节点类:组合对象,通常会存储它的子节点,实现抽象节点所声明的接口。

组合模式的实现

下面我们通过两个示例来演示组合模式的实现。

示例1:餐厅菜单的实现

我们要实现一个菜单系统,分别包含面条、米饭和饮料三种类型的商品,这些商品都具有相同的基本属性,如价格、名称等。同时,我们还要实现对整个菜单的操作,如计算总价格等。

首先我们需要定义一个抽象类MenuItem,来定义各个商品的公共接口:

class MenuItem {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
  getPrice() {
    return this.price;
  }
  getName() {
    return this.name;
  }
  getDescription() {
    throw new Error("该方法必须由子类重写");
  }
  print() {
    console.log(`${this.getName()} - ${this.getPrice()}`);
  }
}

然后,我们定义面条、米饭和饮料三个叶节点类,分别继承MenuItem类并实现各自的公共接口:

class Noodles extends MenuItem {
  constructor(name, price) {
    super(name, price);
  }
  getDescription() {
    return "面条";
  }
}

class Rice extends MenuItem {
  constructor(name, price) {
    super(name, price);
  }
  getDescription() {
    return "米饭";
  }
}

class Drink extends MenuItem {
  constructor(name, price) {
    super(name, price);
  }
  getDescription() {
    return "饮料";
  }
}

最后,我们定义菜单组合类Menu,来组合各个商品节点,并实现菜单的公共接口:

class Menu extends MenuItem {
  constructor() {
    super("菜单", 0);
    this.menuItems = [];
  }
  add(item) {
    this.menuItems.push(item);
  }
  remove(item) {
    const index = this.menuItems.indexOf(item);
    this.menuItems.splice(index, 1);
  }
  getPrice() {
    return this.menuItems.reduce((prev, item) => {
      return prev + item.getPrice();
    }, 0);
  }
  getDescription() {
    return "菜单";
  }
  print() {
    console.log(`------ ${this.getName()} -------`);

    this.menuItems.forEach((item) => {
      item.print();
    });
  }
}

这样,我们就可以创建各种商品节点,并使用菜单组合类将它们组合成为一个树形结构:

const noodles1 = new Noodles("羊肉面", 15);
const noodles2 = new Noodles("牛肉面", 12);
const rice1 = new Rice("鸡蛋炒饭", 11);
const drink1 = new Drink("可乐", 5);
const drink2 = new Drink("奶茶", 8);

const menu = new Menu();
menu.add(noodles1);
menu.add(noodles2);
menu.add(rice1);

const drinkMenu = new Menu();
drinkMenu.add(drink1);
drinkMenu.add(drink2);

menu.add(drinkMenu);

menu.print();  // 输出所有商品的名称和价格
console.log("总价格:", menu.getPrice());  // 输出所有商品的总价格

输出结果如下:

------ 菜单 -------
羊肉面 - 15
牛肉面 - 12
鸡蛋炒饭 - 11
------ 菜单 -------
可乐 - 5
奶茶 - 8
总价格: 51

示例2:文件系统的实现

我们要实现一个文件系统,其中包括了文件和文件夹两种节点,文件节点和文件夹节点具有相同的基本属性,如名称、类型等。同时,我们还要实现对整个文件系统的操作,如计算文件总数等。

同样地,我们需要定义一个抽象类Node,来定义各个节点的公共接口:

class Node {
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }
  getName() {
    return this.name;
  }
  getType() {
    return this.type;
  }
  getSize() {
    throw new Error("该方法必须由子类重写");
  }
  addChild() {
    throw new Error("该方法必须由子类重写");
  }
  removeChild() {
    throw new Error("该方法必须由子类重写");
  }
  getChild() {
    throw new Error("该方法必须由子类重写");
  }
  getChildren() {
    throw new Error("该方法必须由子类重写");
  }
}

然后,我们定义文件节点和文件夹节点两个叶节点类,分别继承Node类并实现各自的公共接口:

class File extends Node {
  constructor(name, size) {
    super(name, "文件");
    this.size = size;
  }
  getSize() {
    return this.size;
  }
}

class Folder extends Node {
  constructor(name) {
    super(name, "文件夹");
    this.children = [];
  }
  addChild(node) {
    this.children.push(node);
  }
  removeChild(node) {
    const index = this.children.indexOf(node);
    if (index !== -1) {
      this.children.splice(index, 1);
    }
  }
  getChild(index) {
    return this.children[index];
  }
  getChildren() {
    return this.children;
  }
  getSize() {
    return this.children.reduce((prev, child) => {
      return prev + child.getSize();
    }, 0);
  }
}

最后,我们定义文件系统组合类FileSystem,将文件和文件夹节点都组合起来,并实现对整个文件系统的操作的公共接口:

class FileSystem extends Folder {
  constructor(name) {
    super(name);
  }
  getFileCount() {
    return this.getChildren().filter((child) => {
      return child.getType() === "文件";
    }).length;
  }
  getFolderCount() {
    return this.getChildren().filter((child) => {
      return child.getType() === "文件夹";
    }).length;
  }
  getSize() {
    return this.children.reduce((prev, child) => {
      return prev + child.getSize();
    }, 0);
  }
}

这样,我们就可以创建各种文件和文件夹节点,并使用文件系统组合类将它们组合成为一个树形结构:

const folder1 = new Folder("目录1");
const folder2 = new Folder("目录2");
const folder3 = new Folder("目录3");

const file1 = new File("文件1", 50);
const file2 = new File("文件2", 20);
const file3 = new File("文件3", 10);

folder1.addChild(file1);
folder1.addChild(folder2);

folder2.addChild(file2);
folder2.addChild(folder3);

folder3.addChild(file3);

const fs = new FileSystem("根目录");
fs.addChild(folder1);

console.log("文件总数:", fs.getFileCount());
console.log("文件夹总数:", fs.getFolderCount());
console.log("文件系统总大小:", fs.getSize());

输出结果如下:

文件总数: 3
文件夹总数: 3
文件系统总大小: 80

总结

组合模式是一种结构型设计模式,可以方便地组织对象成树形结构,实现“部分-整体”的操作。通过两个示例,我们演示了组合模式的实现方法,并学习了其中的一些经验与技巧。这些技巧包括:抽象类的使用、叶节点类的实现、组合节点类的实现等。掌握了这些技巧,我们就可以快速地实现复杂的树形结构操作。

本文标题为:《javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解

基础教程推荐