就当我们感觉我们的设计已经足够好的时候, 新的需求来了, 我们不仅要支持多种菜单, 还要支持菜单下可以拥有子菜单.
例如我想在DinerMenu下添加一个甜点子菜单(dessert menu). 以我们目前的设计, 貌似无法实现该需求.
目前我们无法把dessertmenu放到MenuItem的数组里.
我们应该怎么做?我们需要一种类似树形的结构, 让其可以容纳/适应菜单, 子菜单以及菜单项.
我们还需要维护一种可以在该结构下遍历所有菜单的方法, 要和使用遍历器一样简单.
遍历条目的方法需要更灵活, 例如, 我可能只遍历DinerMenu下的甜点菜单(dessert menu), 或者遍历整个Diner Menu, 包括甜点菜单.
组合模式定义组合模式允许你把对象们组合成树形的结构, 从而来表示整体的层次. 通过组合, 客户可以对单个对象或对象们的组合进行一致的处理.
先看一下树形的结构, 拥有子元素的元素叫做节点(node), 没有子元素的元素叫做叶子(leaf).
针对我们的需求:
菜单Menu就是节点, 菜单项MenuItem就是叶子.
针对需求我们可以创建出一种树形结构, 它可以把嵌套的菜单或菜单项在相同的结构下进行处理.
组合和单个对象是指什么呢?
如果我们拥有一个树形结构的菜单, 子菜单, 或者子菜单和菜单项一起, 那么就可以说任何一个菜单都是一个组合, 因为它可以包含其它菜单或菜单项.
而单独的对象就是菜单项, 它们不包含其它对象.
使用组合模式, 我们可以把相同的操作作用于组合或者单个对象上. 也就是说, 大多数情况下我们可以忽略对象们的组合与单个对象之间的差别.
该模式的类图:
客户Client, 使用Component来操作组合中的对象.
Component定义了所有对象的接口, 包括组合节点与叶子. Component接口也可能实现了一些默认的操作, 这里就是add, remove, getChild.
叶子Leaf会继承Component的默认操作, 但是有些操作也许并不适合叶子, 这个过会再说.
叶子Leaf没有子节点.
组合Composite需要为拥有子节点的组件定义行为. 同样还实现了叶子相关的操作, 其中有些操作可能不适合组合, 这种情况下异常可能会发生.
使用组合模式来设计菜单首先, 需要创建一个component接口, 它作为菜单和菜单项的共同接口, 这样就可以在菜单或菜单项上调用同样的方法了.
由于菜单和菜单项必须实现同一个接口, 但是毕竟它们的角色还是不同的, 所以并不是每一个接口里(抽象类里)的默认实现方法对它们都有意义. 针对毫无意义的默认方法, 有时最好的办法是抛出一个运行时异常. 例如(NotSupportedException, C#).
MenuComponent: