第18讲:Iterator 迭代器模式

2006.7.12 李建忠

集合内部结构与外部访问

image

 

动机(Motivation)

在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。

使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。

 

意图(Intent)

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

——《设计模式》GoF

 

例说Iterator应用

image

下面是迭代器所拥有的最小集合,Current是属性,只能Get不能Set。还有两个方法:MoveNext是往下一个元素走,如果访问到最后一个元素之后没有元素了,就返回False;Reset是复位,回到初始位置。

image

如果有一个容器实现了IEnumerable接口,它就可以支持我们的迭代操作了。

这种设计模式已经内化为C#语言的一种元素了,就是Foreach关键字。

我们定义的容器首先要实现IEnumerable接口,实现的GetEnumerator方法要返回一个IEnumerator的类型的集合。

image

image

Foreach的工作机制

image

遍历代码中访问的全部是接口,而不用关心集合的内部结构。上面的代码等同于下面的Foreach。

image

MyEnumerator集合类可以写成private私有的,外部不可以访问,只暴露接口给外部访问。

 

结构(Structure)

image

Aggregate表示集合结构接口,对应例子中的IEnumerable,Iterator表示迭代器接口,对应IEnumerator。First()方法对应Reset(),Next()和IsDone对应MoveNext(),CurrentItem()对应Current属性。Aggregate和Iterator的具体实现都只依赖于接口,而不依赖于具体实现,这样就把两者之间的耦合降低到最小。

 

Iterator模式的几个要点

迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。

迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。

例如假设我们有一个求和算法

image

它可以操作在多个支持迭代器的集合上,如果把这个算法写成ArrayList的话,就会非常受限制。同时我们更可以用C#的Foreach语句来写。

image

我们的算法应该是独立的,写的时候应该尽量操作接口,这样我们写好一个算法,就能应对N种集合的变化,使得同样的算法能在不同的集合上操作。

迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。

也就是说,我们在迭代的时候,应该只是读取操作,不能更改容器的接口,例如遍历的时候删除一个元素,这样是不可以的。容器的结构师绝对不能碰的,一旦结构更改,遍历就会出问题。

image

下面这种情况对i进行更改,是不会影响原来的数组内容的,因为i是int类型,它只是一份拷贝。

image

image

我们一定要给用户提供尽量纯只读的迭代。保证每个元素被且只被遍历一次。

 

.NET 2.0中的Iterator

image

yield return关键字能够支持迭代的简化,动态生成迭代的类型。如果反编译的话,能看见.NET内部能够帮助我们构造一个之前我们写的类,实现相应方法。虽然实现层面有所不同,但还是遵循了整体的迭代器定义的接口。能够支持迭代的。

目前我们讨论的迭代器游标都是往前走的,但我们也可以扩充方法,让游标可以往回走,也可以设定游标的步长等等。

2010.10.17

posted @ 2010-10-17 16:41  山天大畜  阅读(1107)  评论(0编辑  收藏  举报