《Head First设计模式》 读书笔记10 迭代器与组合模式
The Iterator and Composite Patterns
问题引入
餐厅和煎饼屋要合并,要把煎饼屋的菜单作为早餐菜单,餐厅的菜单作为午餐菜单。
但是对于菜单项的记录,前者用的是ArrayList,后者用的是数组,两者都不愿意改变代码实现。
所以在女招待处理的时候,需要用不同的方法分别处理这两个菜单,毕竟菜单项的返回值一个是ArrayList一个是数组,遍历的时候也要分别遍历。
之前的学习中一直说要封装变化的部分,但是由不同的集合类型所造成的遍历也可以封装吗?
迭代器模式就是解决这个问题。
迭代器模式
迭代器模式(Iterator Pattern)依赖于一个名为迭代器的接口。
注:聚合与集合同义。
现在,一旦有了这个接口,就可以为各种对象集合实现迭代器:数组、列表、散列表……
用迭代器模式解决菜单问题
创建菜单迭代器类,实现共同的迭代器接口,然后在菜单类中包含createIterator()方法,返回迭代器接口。
迭代器让女招待能够从具体类的实现中解耦,她只关心她能够取得迭代器。
如下图:
进一步改良的设计,给菜单一个共同的接口,叫做Menu,Menu接口中包含createIterator()方法。
这样女招待就不再依赖具体菜单,也不依赖菜单项的实现,只关心菜单和迭代器这两个接口。
迭代器模式定义
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式的类图:
单一责任
如果允许我们的聚合实现它们内部的集合以及相关的操作和遍历的方法,又会如何?
这样做不好,因为这样我们给了这个类两个变化的原因:如果这个集合改变,这个类必须跟着改变;如果遍历的方式改变,这个类也必须跟着改变。
设计原则:一个类应该只有一个引起变化的原因。
这个原则告诉我们,应该尽量让每个类保持单一责任。
问题2引入
餐厅要创建一份甜点菜单,并将它放入常规的菜单中,即要支持菜单中的菜单。
所以,在新的设计中,我们需要:
1.某种树形结构,可以容纳菜单、子菜单和菜单项;
2.需要确定能够在每个菜单的各个项之间游走,并且要像现在使用迭代器一样方便;
3.需要能够更有弹性地在菜单项之间游走。比方说,可能只需要遍历甜点菜单,或者可以遍历餐厅的整个菜单(包括甜点菜单)。
这里就要引入组合模式。
组合模式定义
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
使用组合结构,我们能把相同的操作应用在组合和个别对象上。
换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
组合模式类图
组合包含组件,组件有两种:组合与叶节点元素。
利用组合设计菜单
需要创建一个菜单组件(MenuComponent)来作为菜单(Menu)和菜单项(MenuItem)的共同接口,让我们能够用统一的做法来处理菜单和菜单项。
所有的组件都必须实现MenuComponent接口,然而,叶节点和组合节点的角色不同,所以有些方法可能并不适合某种节点。
面对这种情况,可以让这样的方法不做事,或者返回null或者false,有时候,最好是抛出运行时异常。
在实现组合模式时,有许多设计上的折衷,你要根据需要平衡透明性和安全性。
组合迭代器
CompositeIterator是一个迭代器,它的工作是遍历组件内的菜单项,而且确保所有的子菜单(以及子子菜单……)都被包括进来。
这是一个外部的迭代器,必须维护它在遍历中的位置,所以在组合层次结构中上上下下时,使用了堆栈。