引入:最近开发过程中,遇到场景组、场景嵌套的情况,看到代码中运用了组合模式。于是乎,温习了组合模式的应用场景和设计过程。同时,在组合模式运用的过程中,很自然地会出现遍历的情况,这时候迭代器模式就有用武之地。
组合模式
为了更好地说明,我用一个生活中的case进行阐述。菜单和子菜单是生活中常见的名称。菜单包含子菜单和其他选项,子菜单里会有子子菜单和其他选项,依次类推,嵌套结构。
如下:1
2
3
4
5
6
7
8
9
10
11
12文档菜单
- 新标签页
- 打开
- 通过链接打开
- 最近使用
- 按日期
- 使用记录
- 分享
- 钉钉分享
- Email分享
- 微信分享
- 关闭
我们如何在系统中表达这种关系,同时让使用者(调用方)不需要理解其中的细节就可以直接处理其中的子菜单和其他选项呢?
其实,这种情况就非常适合使用组合模式,组合模式允许系统将对象组合成树形结构来表现“整体|部分”的层次结构。优点是能让客户(使用者)以统一的方式处理其中的个别对象(其他选项)和对象组合(子菜单)。
思路如下:
为了使“子菜单”和“其他选项”在形式上实现统一,我们需要定义一个统一的接口对外提供使用,同时规范“子菜单”与“其他选项”的具体实现。对于这个接口,我们希望有一些默认的实现,故而在这里使用抽象类作为这个“接口”。
如图1 组合模式类图所示,Menu代表子菜单,MenuItem代表其他选项,BaseMenuComponent是统一抽象类。MenuVisitor是使用者。Menu和MenuItem都继承了BaseMenuComponent,Menu同时也会使用List
问题&思考:
Q:我们都知道单一职责原则:一个类只有一个责任,但是,这么设计的情况下,一个类有两个职责了:1. 菜单的固有操作;2. 维护层级关系。这岂不是违反的单一职责原则了吗?
A:其实,组合模式就是用这种方式,以违反单一职责原则的方式换取透明性,List
Q:BaseMenuComponent同时具体两种类型的操作,包括“子菜单”和“其他选项”,这种情况下,“子菜单”中不就继承了自己不需要的“其他选项”里面的方法了吗?会造成冗余和数据不安全的问题。
A:没错,会产生上述问题。如果要解决上述问题,就需要将责任区分开来放在不同的接口。这么设计会比较安全,但也因此失去“透明性”。回到问题的初衷:让使用者(调用方)不需要理解其中的细节就可以直接处理其中的子菜单和其他选项。为了不让用户使用条件语句和instanceof操作处理不同类型的问题,所以采用了以上设计。换一种理解方式,可以把MenuItem看成没有子菜单的Menu。
迭代器模式
在软件系统中,有很多方式来表达集合的概念,比如:数组、堆栈等。但是如果调用方如何遍历集合中的对象,不应该需要访知道这些对象的具体实现。而是用通用的方式去遍历这些集合种的对象,所以遍历的这种操作应该被抽象出来。
为了解决遍历集合的问题,出现了迭代器模式。
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其中内部的表示。迭代器的突出优点是,将遍历元素的能力交给迭代器,而不是聚合对象,让聚合的接口更简洁。如图2 迭代器类图所示。
如图2所示,迭代器模式中, Aggregate是供所有具体ConcreteAggregate类实现的接口,使用户Client不再依赖于具体的ConcreteAggregate实现。Aggregate中创建Iterator进行遍历访问。
调用代码实例如下1
2
3
4
5Iterator iterator = aggregate.createIterator();
while(iterator.hasNext()){
Item item = iterator.next();
// 以下可以进一步操作
}