设计模式:组合模式

 意图

组合模式(Composite)是一种结构型设计模式 你可以使用它将对象组合成树状结构 并且能像使用独立对象一样使用它们

组合模式结构

组合设计模式的结构

组件 Component 接口描述了树中简单项目和复杂项目所共有的操作

叶节点 Leaf 是树的基本结构 它不包含子项目

一般情况下 叶节点最终会完成大部分的实际工作 因为它们无法将工作指派给其他部分

容器 Container——又名 组合 Composite——是包含叶节点或其他容器等子项目的单位 容器不知道其子项目所属的具体类 它只通过通用的组件接口与其子项目交互

容器接收到请求后会将工作分配给自己的子项目 处理中间结果 然后将最终结果返回给客户端

客户端 Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。

组合模式适合应用场景

  • 如果你需要实现树状对象结构 可以使用组合模式

组合模式为你提供了两种共享公共接口的基本元素类型 简单叶节点和复杂容器 容器中可以包含叶节点和其他容器 这使得你可以构建树状嵌套递归对象结构

  • 如果你希望客户端代码以相同方式处理简单和复杂元素 可以使用该模式

组合模式中定义的所有元素共用同一个接口 在这一接口的帮助下 客户端不必在意其所使用的对象的具体类

组合模式优缺点

优点:

  • 你可以利用多态和递归机制更方便地使用复杂树结构
  •  开闭原则 无需更改现有代码 你就可以在应用中添加新元素 使其成为对象树的一部分

缺点:

  • 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。

与其他模式的关系

  • 桥接模式 状态模式策略模式 在某种程度上包括适配器模式 模式的接口非常相似 实际上 它们都基于组合模式——即将工作委派给其他对象 不过也各自解决了不同的问题 模式并不只是以特定方式组织代码的配方 你还可以使用它们来和其他开发者讨论模式所解决的问题

  • 你可以在创建复杂组合树时使用生成器模式 因为这可使其构造步骤以递归的方式运行

  • 责任链模式通常和组合模式结合使用 在这种情况下 叶组件接收到请求后 可以将请求沿包含全体父组件的链一直传递至对象树的底部

  • 你可以使用迭代器模式来遍历组合

  • 你可以使用访问者模式对整个组合树执行操作

  • 你可以使用享元模式实现组合树的共享叶节点以节省内存

  • 组合装饰模式的结构图很相似 因为两者都依赖递归组合来组织无限数量的对象

    装饰类似于组合 但其只有一个子组件 此外还有一个明显不同 装饰为被封装对象添加了额外的职责 组合仅对其子节点的结果进行了 求和

    但是 模式也可以相互合作 你可以使用装饰来扩展组合树中特定对象的行为

  • 大量使用组合装饰的设计通常可从对于原型模式的使用中获益 你可以通过该模式来复制复杂结构 而非从零开始重新构造

在 Java 中使用模式

使用实例 组合模式在 Java 代码中很常见,常用于表示与图形打交道的用户界面组件或代码的层次结构

下面是一些来自 Java 标准程序库中的组合示例

示例代码

组件 Component)

public class Employee {
  private final String name;
  private final List<Employee> subordinates = new ArrayList<>();

  public Employee(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void add(Employee employee) {
    subordinates.add(employee);
  }

  public List<Employee> getSubordinates() {
    return subordinates;
  }
}

叶节点 Leaf)

public class Manager extends Employee {
  public Manager(String name) {
    super(name);
  }
}

客户端 Client)

public class Client {
  public static void main(String[] args) {
    Employee alice = new Employee("Alice");
    Employee bob = new Employee("Bob");
    Manager richard = new Manager("Richard");
    richard.add(alice);
    richard.add(bob);

    Manager ceo = new Manager("Michel");
    ceo.add(richard);

    for (Employee manager : ceo.getSubordinates()) {
      System.out.println(manager.getName() + ":");
      for (Employee employee : manager.getSubordinates()) {
        System.out.println(employee.getName());
      }
    }
  }
}