组合模式¶
分类:结构型模式。
类结构¶
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 示例包中的组合迭代器。聚合关系表示子组件集合,不额外宣称源码强制独占子节点或统一销毁其生命周期。
协作过程¶
MenuTestDrive创建allMenus,加入早餐、午餐和晚餐菜单,再把dessertMenu加入午餐菜单dinerMenu。- 各级
Menu.add()将MenuItem或另一个Menu保存进同一个menuComponents集合;甜点菜品保存在dessertMenu中。 - 入口把
allMenus传给Waitress,调用printMenu();服务员只调用根组件的print()。 - 每个
Menu.print()先打印自身信息,再遍历子组件并调用它们的print()。遇到子菜单继续递归,遇到MenuItem就打印菜品信息,完成整棵树的输出。
固定版本源码: