在上一篇“调侃《Head First设计模式》之迭代器和组合模式(一)”主要讲了用类封装集合或者数组的迭代器,使得客户端可以方便遍历集合数组元素提高程序的可扩展性。这次继续按照它的故事,引入新的模式:组合模式。
之前菜单的结构是一个菜单包含着几个菜单项,但现在业务需要,菜单中也要包含子菜单(如下图),那该如何实现方便呢?
看,餐厅菜单中又包含着甜点菜单。这样,之前的菜单类一定要修改了,如何修改才可以方便迭代和扩展性高呢?
容易想到,现在整个菜单结构成为树形结构,如下图:
要迭代所有元素,就必须能在树形结构的所有元素间游走。
要方便地迭代元素,最好的方式是对于菜单项和子菜单的迭代方式统一,而可以做到这点的,可以使用组合模式。这次我们先看模式的定义:
组合模式:允许你将对象组合成树形结构类表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及组合对象。
类图如下:
看,菜单就像这里的Composite,它可以拥有子菜单,菜单项就是Leaf,已经没有子菜单了。它们都继承Component,并且实现对应的方法(比如菜单是没有增加删除子项功能的,所有没有实现add和remove方法)
开始利用组合模式设计菜单(类图如下):
菜单项(MenuItem)和菜单(Menu)都继承于MenuComponent,它们根据自己的需要选择性实现一些方法。为什么这么设计呢?你可能已经猜到了,可以利用多态使得它们可以统一处理。
上代码:
菜单和菜单项统一的抽象类:
之所以每个方法都抛异常,是因为一旦子类没有实现的方法被调用,会抛异常作为提醒。
菜单项:
菜单:
这样实现的print方法其实很有问题,根本不能实现打印的遍历,打印所有菜单项。利用之前的迭代器,修改如下:
利用迭代器遍历菜单项,如果是子菜单,则递归地调用子菜单的print,这样通过迭代,调用根菜单就可以一次将所有菜单打印出来啦!
这样侍女的代码就变得很简单了,只需要一个菜单就可以了:
之前讲过单一责任原则,你是否觉得MenuComponent这个类违背了这个原则呢?是的,组合模式这样设计确实有所违背该原则,它利用单一原则换取透明性,也就是菜单项和子菜单对于客户来说是透明的,这样客户端的处理可以一视同仁。但是这样客户端有机会对一个元素做一些不恰当的操作(菜单添加到菜单项),因此会失去一些安全性。为了安全性我们可以采取一些条件判断语句,但是这样又失去了透明性。
这样实现看起来好像没有问题了,但是由于迭代是在print内部的,如果侍女要获得一个具体的菜单或者菜单项,还需要修改。
根据上一篇迭代器的设计,可以从基类MenuComponent入手,增加createIterator方法:
菜单就返回一个CompositeIterator对象(具体下面那上说),菜单项则返回空迭代器。
CompositeIterator类利用递归实现树形结构的迭代,这很像数据结构中树的遍历,它利用堆栈类保存当前迭代器,当前迭代器遍历结束后弹出,又使用堆栈顶部的,即上一层菜单的迭代器。该类代码不多,但是要读懂需要好好琢磨。
通过堆栈成功维护了它在遍历中的位置,以便客户可以通过调用hasNext和next来实现遍历。
空迭代器:
就是什么事都不做。
这样一来,得到一个外部迭代器,侍女可以亲自遍历菜单了!