组合设计模式在Js中的应用

735 阅读5分钟

组合设计模式

定义

  • 一种结构型设计模式,它允许我们将对象组合成树形结构以表示"部分-整体"的层次结构。
  • 在这种模式中,单个对象和组合对象都被当作相同类型的对象对待,从而使客户端可以统一地处理它们。

参与者

1.\color{blue}{1.} 组件(Component):定义组合对象的通用接口,声明了可以管理子组件的方法,例如增加、删除、获取子组件等。

2.\color{blue}{2.} 叶子节点(Leaf):表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但是对于管理子组件的方法没有实际操作。

3.\color{blue}{3.} 容器节点(Composite):表示组合中的容器节点对象,容器节点包含子节点。它实现了组件接口的方法,并且对于管理子组件的方法进行了具体实现。

作用

  • 使用组合设计模式可以方便地处理复杂对象的层次结构,

  • 同时也能够使客户端代码更加简洁

  • 不需要关心处理的是单个对象还是组合对象,常用于树形结构的建模,例如文件系统、图形界面等场景。

示例

interface Component {
  add(component: Component): void | never;
  remove(component: Component): void | never;
  getChild(index: number): Component | null | never;
  operation(): void;
}

class Leaf implements Component {
  operation(): void {
    console.log("执行叶子节点操作");
  }

  add(component: Component): never {
    throw new Error(`叶子节点没有子节点,不需要实现该方法`);
  }

  remove(component: Component): never {
    throw new Error(`叶子节点没有子节点,不需要实现该方法`);
  }

  getChild(index: number): never {
    throw new Error(`叶子节点没有子节点,返回null`);
  }
}

class Composite implements Component {
  private children: Component[] = [];

  operation(): void {
    console.log("执行容器节点操作");
    for (const child of this.children) {
      child.operation();
    }
  }

  add(component: Component): void {
    this.children.push(component);
  }

  remove(component: Component): void {
    const index = this.children.indexOf(component);
    if (index !== -1) {
      this.children.splice(index, 1);
    }
  }

  getChild(index: number): Component | null {
    if (index >= 0 && index < this.children.length) {
      return this.children[index];
    } else {
      return null;
    }
  }
}

const root = new Composite();
const leaf1 = new Leaf();
const leaf2 = new Leaf();
const composite = new Composite();
const leaf3 = new Leaf();

root.add(leaf1);
root.add(composite);
composite.add(leaf2);
composite.add(leaf3);

// 执行根节点操作,会递归执行子节点的操作
root.operation();

有趣的例子

interface Role {
  getName(): string;
  getAttributes(): string[];
  useSkill(): void;
}

class Warrior implements Role {
  getName(): string {
    return "战士";
  }

  getAttributes(): string[] {
    return ["力量", "耐力"];
  }

  useSkill(): void {
    console.log("战士释放技能:猛击");
  }
}

class Mage implements Role {
  getName(): string {
    return "法师";
  }

  getAttributes(): string[] {
    return ["智力", "魔法"];
  }

  useSkill(): void {
    console.log("法师释放技能:火球术");
  }
}

class Thief implements Role {
  getName(): string {
    return "盗贼";
  }

  getAttributes(): string[] {
    return ["敏捷", "隐身"];
  }

  useSkill(): void {
    console.log("盗贼释放技能:潜行");
  }
}

class Party implements Role {
  private members: Role[] = [];

  getName(): string {
    return "队伍";
  }

  getAttributes(): string[] {
    const attributes: string[] = [];
    for (const member of this.members) {
      attributes.push(...member.getAttributes());
    }
    return attributes;
  }

  useSkill(): void {
    console.log("队伍释放技能:全体攻击");
    for (const member of this.members) {
      member.useSkill();
    }
  }

  addMember(member: Role): void {
    this.members.push(member);
  }

  removeMember(member: Role): void {
    const index = this.members.indexOf(member);
    if (index !== -1) {
      this.members.splice(index, 1);
    }
  }
}

const warrior = new Warrior();
const mage = new Mage();
const thief = new Thief();

const party = new Party();
party.addMember(warrior);
party.addMember(mage);
party.addMember(thief);

console.log(party.getName()); // 输出:队伍
console.log(party.getAttributes()); // 输出:["力量", "耐力", "智力", "魔法", "敏捷", "隐身"]

warrior.useSkill(); // 输出:战士释放技能:猛击
mage.useSkill(); // 输出:法师释放技能:火球术
thief.useSkill(); // 输出:盗贼释放技能:潜行

party.useSkill(); // 输出:队伍释放技能:全体攻击
// 输出:
// 战士释放技能:猛击
// 法师释放技能:火球术
// 盗贼释放技能:潜行

应用场景

1.\color{blue}{1.} UI 组件库:UI 组件库通常包含各种复杂的组件,如按钮、表单、卡片等。这些组件可以被看作是一个组合结构,每个组件可以包含其他组件或嵌套组件。使用组合设计模式,我们可以定义一个通用的组件接口,并让每个组件实现该接口。这样,在使用组件库时,用户可以方便地处理单个组件和组件的组合。

2.\color{blue}{2.} 导航菜单:网站或应用程序中的导航菜单通常是一个树形结构,包含多个菜单项和子菜单。通过使用组合设计模式,我们可以将每个菜单项和子菜单都视为组件,并统一对待它们。这样,在渲染导航菜单时,我们可以递归地遍历菜单组件,根据其类型进行相应的渲染和交互处理。

3.\color{blue}{3.} 数据可视化图表:数据可视化图表通常由多个元素组成,如坐标轴、图例、数据点等。通过使用组合设计模式,我们可以将每个图表元素都视为组件,并按照一定的层次结构组织起来。这样,在绘制图表时,我们可以递归地遍历图表组件,并根据其类型进行相应的绘制和交互操作。

4.\color{blue}{4.} 嵌套路由:在前端框架中,如 React、Vue 等,通常会使用嵌套路由来实现页面的层级结构。每个路由可以包含其他子路由或页面组件。使用组合设计模式,我们可以将每个路由都视为一个组件,并统一对待它们。这样,在定义和管理路由时,我们可以递归地遍历路由组件,根据其类型进行相应的路由配置和导航处理。

快速记忆

如何快速记住这个设计模式呢?实际上组合设计模式的本质就是【模块化】,在生活中最常见的例子就是,快餐店的套餐了,套餐中的薯条,汉堡,炸鸡腿都可以看成是继承自【食物类】的扩展类,而豪华套餐又可以包括A套餐和B套餐等,而吃掉套餐本质上是吃掉套餐中的薯条、炸鸡、汉堡等每一个元素。