迭代器 & 组合模式
现实案例
- 现在有两个餐馆。他们各自实现了自己的餐点类;
- 虽然两个人用了相同的菜单项类 MenuItem (单个菜的类);
- 但是两个人用了不同的方式储存各自的菜单项;
- A 用了 ArrayList();
- B 用了数组;


- 如果想要同时打印出两个菜单的所有菜单项,就要实现两种不同的遍历方式;
- 如果又有其他店家采用了新的储存方式,我们就有要新加一种遍历方式;


上面实现的问题
- 遍历代码是针对两个菜单类的具体实现来编码的,而不是针对接口;
- 遍历代码需要知道每个菜单类内部是如何储存菜单项集合的,这违反了封装;
- 违反了开放-封闭原则,如果另一种储存方式的菜单类出现,我们就要改遍历代码;
- 有重复代码,上面用来两个 for-loop;
迭代器模式
- 迭代器模式:提供一种方法顺序访问一个聚合对象中的每个元素,而又不暴露其内部的实现;
- 我们需要创造一个迭代接口,接口中定义了用于遍历的方法;
- 之后再定义迭代器类,让它们去针对不同的集合类型,去实现迭代接口;
- 通过迭代模式,客户使用一种接口,透过迭代器对象就可以遍历不同类型的集合;

用迭代器改写案例

- 改写菜单类,增加方法
createIterator
去创建迭代器实例并返回;


- 上面的代码 Waitress 类和两个菜单类耦合在了一起;
- 因为两个菜单类都有相同的
createIterator
方法,所以我们可以抽象出一个 Menu 接口; - 这样让 Waitress 类针对 Menu 接口编程,就可以降低耦合性了;


单一职责
- 上面的例子中,菜单类只负责储存菜单项集合,并不符合遍历集合;
- 如果我们让菜单类,即储存集合,又负责遍历集合;
- 那么我们就给了他两个变化的原因。集合储存方式改变时,我们要改变菜单类,遍历方式改变时,菜单类也要改变;
- 单一职责:一个类应该只有一个引起变化的原因;
- 当类的变化原因增多时,出现 BUG 的几率也增加;
- 当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有 “高内聚”;
- 内聚用来度量一个类或模块,紧密地达成单一职责的程度;
- 🌰 下面 👇 的 Game 类就可以拆分成另外三个类:

现实案例
- 现在菜单出现了新的变化;
- 菜单之间可以嵌套;
- 我们需要能够在遍历所有的菜单上的菜单项;
- 也需要能够遍历指定的某个菜单;

组合模式
- 组合模式:允许你将对象组合成树形结构,来表现 “整体/部分” 的层次结构。组合能让客户已一致的方式处理个别对象以及组合对象;
- 组合模式让我们能够用树形方式创建对象的结构,树里面包含了组合以及个别的对象;
- 使用组合结构,我们能把相同的操作应用在组合和个别对象上;
- 也就是,在大多数情况下,我们可以 “忽略” 对象组合和个别对象之间的差别;
用组合模式设计菜单

- 先来实现菜单组件 MenuComponent;
- 菜单组件提供了一个接口,让菜单项(叶节点)和菜单(组合节点)共同使用;
- 菜单组价是个抽象类,里面定义了一堆接口,有的是给菜单项用的,有的是给菜单用的;
- 在菜单组件中,让方法默认抛出一个异常,这样如果菜单项或菜单不支持这个操作,他们不同做任何事情,直接继承实现就行了,默认就是抛出异常;



- 注意菜单类的
print
方法的实现; - 因为菜单是一个组合,其中包括菜单项和其他的菜单;
- 为了打印出它内部包含的所有菜品,我们采用递归的形式来实现;
- 先用迭代器来遍历集合中的每一项;
- 如果是菜单项就直接调用它的
print
方法; - 如果是其他菜单,就调用它的
print
方法,进入一个新的遍历; - 直到最后所有的项都遍历完,打印结束;

迭代器 + 组合模式
- 在 👆 上面的例子中,我们实现了菜单类的
print
方法。可以递归地打印出所有的菜品; - 但是整个遍历都是自动的,我们不能再此过程中追踪每一个菜品;
- 我们希望能让客户可以灵活地控制遍历的每一步;
- 🌰 例如,服务员希望游走整个菜单,然后挑出所有的素菜;
- 通过 “迭代器 + 组合模式” 的形式,我们可以实现这个功能;
具体实现
- 之前的菜单组件 MenuComponent 添加方法
createIteration
用以返回迭代器;

- 之后在 Menu 和 MenuItem 中分别实现这个方法:

- 因为菜单项 MenuItem 没什么可遍历的,所以返回一个空迭代器;
- 空迭代器的
hasNext()
永远返回 false;

- 实现组合迭代器 CompositeIterator;

实现素食菜单
- 现在有了追踪遍历过程的能力后。我们就可以挑选出所有的素食了;
