跳转至

组合模式

分类:结构型模式。

类结构

classDiagram
    direction TB
    class Client
    class Component {
        <<interface>>
        +operation()
    }
    class Leaf {
        +operation()
    }
    class Composite {
        +operation()
        +add(child)
        +remove(child)
    }
    Component <|.. Leaf
    Component <|.. Composite
    Composite o-- "0..*" Component : 子组件
    Client ..> Component : 使用

角色定义

角色 定义
Client(客户端) 通过组件接口统一调用单个叶子或组合对象的操作。
Component(组件接口) 只定义叶子与组合共有的业务操作,不要求叶子实现子节点管理方法。
Leaf(叶子) 实现组件操作,表示不包含子节点的基本对象。
Composite(组合节点) 实现组件操作并递归委托给子组件;聚合零个或多个组件,子组件可以是叶子或组合节点。仅此角色提供添加、移除子节点的方法,图中聚合不限定子节点生命周期。

Head First 示例:菜单中嵌套子菜单

《Head First 设计模式》第 9 章将餐厅菜单组织成树:总菜单包含早餐、午餐和晚餐菜单,午餐菜单还能包含甜点子菜单,最末端才是具体菜品。这里核对的是作者 2020 年第二版配套代码 composite/menu;该固定版本的实际目录名是单数 menu。服务员只持有根节点,通过一次 printMenu() 打印整棵菜单树。

本例与上方通用图的安全式组合略有不同:抽象类 MenuComponent 同时提供业务查询和 add()、remove()、getChild(),使叶子与组合节点拥有统一接口,属于透明式组合。这些方法默认抛出 UnsupportedOperationException;Menu 覆盖子节点管理方法,MenuItem 保留这些默认实现,因此对菜品调用 add() 虽能通过类型检查,运行时仍会失败。

具体类名 对应角色 职责
MenuComponent 抽象组件 统一菜单和菜品的查询、打印及子节点管理方法,默认拒绝不支持的操作。
Menu 组合节点 用 menuComponents 保存子组件,提供增删查,并在 print() 中递归打印子组件。
MenuItem 叶子 保存菜名、描述、素食标记和价格,打印单个菜品,不管理子节点。
Waitress 客户端 持有 MenuComponent allMenus,将 printMenu() 委托给根节点的 print()。
MenuTestDrive 组装入口 创建总菜单、各级子菜单和菜品,再把总菜单交给服务员。

示例 UML

classDiagram
    direction TB
    class MenuComponent {
        <<abstract>>
        +add(MenuComponent menuComponent)
        +remove(MenuComponent menuComponent)
        +getChild(int i) MenuComponent
        +getName() String
        +getPrice() double
        +isVegetarian() boolean
        +print()
    }
    class Menu {
        ArrayList~MenuComponent~ menuComponents
        +add(MenuComponent menuComponent)
        +remove(MenuComponent menuComponent)
        +getChild(int i) MenuComponent
        +print()
    }
    class MenuItem {
        String name
        boolean vegetarian
        double price
        +getPrice() double
        +isVegetarian() boolean
        +print()
    }
    class Waitress {
        MenuComponent allMenus
        +printMenu()
    }
    class MenuTestDrive {
        +main(String[] args)$
    }
    MenuComponent <|-- Menu
    MenuComponent <|-- MenuItem
    Menu o-- "0..*" MenuComponent : menuComponents
    Waitress --> MenuComponent : allMenus 根节点
    MenuTestDrive ..> Menu : 组装菜单层级
    MenuTestDrive ..> MenuItem : 添加菜品
    MenuTestDrive ..> Waitress : 传入根节点

图中省略构造器、部分描述字段与查询方法,以及 ArrayList、Iterator 的库类型节点。这里选取菜单自身递归打印的版本,不引入另一个 composite/menuiterator 示例包中的组合迭代器。聚合关系表示子组件集合,不额外宣称源码强制独占子节点或统一销毁其生命周期。

协作过程

  1. MenuTestDrive 创建 allMenus,加入早餐、午餐和晚餐菜单,再把 dessertMenu 加入午餐菜单 dinerMenu。
  2. 各级 Menu.add() 将 MenuItem 或另一个 Menu 保存进同一个 menuComponents 集合;甜点菜品保存在 dessertMenu 中。
  3. 入口把 allMenus 传给 Waitress,调用 printMenu();服务员只调用根组件的 print()。
  4. 每个 Menu.print() 先打印自身信息,再遍历子组件并调用它们的 print()。遇到子菜单继续递归,遇到 MenuItem 就打印菜品信息,完成整棵树的输出。

固定版本源码:

评论