第五部分 抽 象 集 合
第13章 组 合
组合模式: 将对象组合成树形结构以表示“部分-整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性。
何时使用组合模式
- 想获得对象抽象的树形表示(部分-整体层次结构);
- 想让客户端统一处理组合结构中的所有对象。
在cocoa touch 框架中使用组合模式
在cocoa touch 框架中,UIView被组织成一个组合结构。每个UIView的实例可以包含UIView的其他实例,形成统一的树形结构。让客户端对单个UIView对象和UIView的组合统一对待。
窗口中的UIView在内部形成视图的树形结构。层次结构的根部是一个UIWindow对象和它的内部视图。添加进来的其他UIView成为了它的子视图。它们的每一个可以包含其他视图而变成自己的子试图的超视图。 UIView对象只能有一个超视图,可以有零到多个子试图。
总结:
组合模式的主要意图是让树形结构中的每个节点具有相同的抽象接口。这样整体结构可作为一个统一的抽象结构使用,而不暴露其内部表示。对每个节点的任何操作,可以通过协议或抽象基类中定义的相同接口来进行。
组合结构的内部表示不应暴露给客户端,因此组合模式总是跟迭代器模式一起使用,以遍历组合对象中的每一个项目。迭代器模式和相关的设计问题将在下一章中讨论。
第14章 迭 代 器
在面向对象软件中,针对抽象集合迭代行为的设计模式叫做迭代器。本章将讨论这一模式的思想,并通过cocoa touch 基础框架实现各种类型的迭代器。
何为迭代器模式
迭代器提供了一种顺序访问聚合对象(集合)中元素的方法,而无需暴露结构的底层表示和细节。遍历集合中元素的职能从集合本身转移到迭代器对象。迭代器定义了一个用于访问集合元素并记录当前元素的接口。不同的迭代器可以执行不同的遍历策略。
迭代器:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
基本上有两种迭代器:外部迭代器和内部迭代器。外部迭代器让客户端直接操作迭代过程,所以客户端需要知道外部迭代器才能使用。另一种情况是,集合对象(被迭代的目标对象)在其内部维护并操作一个外部迭代器。提供内部迭代器的典型的集合对象为客户端定义一个接口,或者从底层的集合一次访问一个元素,或者向每个元素发送消息。
两者的区别:
外部迭代器: 客户端需要知道外部迭代器才能使用,但是它为客户端提供了更多的控制;客户端创建并维护外部迭代器;客户端可以使用不同外部迭代器实现多种类型的遍历。
内部迭代器:客户端不需要知道任何外部迭代器,而是可以通过集合对象的特殊接口,或者一次访问一个元素,或者集合中的每个元素发送消息;集合对象本身创建并维护他的外部迭代器;集合对象可以在不修改客户端代码的情况下,选择不同的外部迭代器。
何时使用迭代器模式
- 需要访问组合对象的内容,而又不暴露其内部表示;
- 需要通过多种方式遍历组合对象;
- 需要提供一个统一的接口,用来遍历各种类型的组合对象。
在cocoa touch框架中使用迭代器模式
基础框架中NSEnumerator类实现了迭代器模式。抽象NSEnumerator类的私有具体子类返回枚举其对象,能够顺序遍历各种集合——数组、集set 、字典(键值),把集合中的对象返回给客户端。
NSDirectoryEnumerator是个关系比较远的类。这个类的实例递归枚举文件系统中一个目录的内容。
NSArray、NSSet和NSDictionary这样的集合类,定义了返回与集合的类型相应的NSEnumerator子类实例的方法。所有的枚举器都以同样的方式工作。可以在一个循环中枚举器发送nextObject消息,从枚举器取得对象。
说明:在遍历时修改聚合体对象可能有危险。如果向聚合体添加或从聚合体删除了元素,可能导致对一个元素访问两次或完全漏掉一个元素。简单的办法是对聚合体进行一个深复制,再对副本进行遍历,但是如果创建与存储聚合体的另一个影响性能,代价就比较大了。有很多方法可以实现不受元素插入与删除影响的迭代器。大部分依靠向聚合体注册迭代器。一种实现方法是在插入与删除操作时,聚合体或者调整由它生成的迭代器的内部状态,或者在内部维护信息,以保证正确的遍历。
总结:
迭代器模式与访问者模式有些类似,尤其是把遍历算法放到访问者模式中或者在遍历聚合体时让内部迭代器对元素执行操作的时候。其他相关的模式有组合、工厂方法和备忘录。组合模式常常依靠迭代器来遍历其递归结构。多态的迭代器依靠工厂方法来实例化适当的迭代器具体子类。有时,备忘录跟迭代器模式一起使用。迭代器可以使用备忘录来截取迭代的状态。迭代器在内部保存备忘录,在以后从它恢复其内部状态。
本章讨论了主要和抽象集合及其遍历有关的几个设计模式。接下来将要讨论的几个设计模式,使用对现有设计影响最小的方式,扩展抽象集合或其他类型对象的行为。