迭代器(Iterator) 模式 —— 由 C# 的 foreach 想到的
动机:
在软件的构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,
我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;
同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。
使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”
提供了一种优雅的方式。
意图:
提供一种方法顺序访问一个集合对象中的各个元素,而不暴露该对象的内部表示。
————《设计模式》GOF
GOF 提供的类图:
迭代器模式的几个要点
1. 迭代抽象:访问一个集合对象的内容而无须暴露它的内部表示。
2. 迭代多态:
为遍历不同集合结构提供一个统一的接口,从而支持两样的算法在不同集合
结构上进行操作。
3. 迭代器的健壮性考虑:
遍历的同时更改迭代器所在的集合结构,可能会导致问题。
现举一例,
实现一个集合类MyCollection,内部数据结构采用一个带有头结点的单向链表。
为了更清楚地说明问题,所有的接口和类都重新定义,不使用FCL已有的结构,
Reference 只要System 即可。[根据C#的语法特点,更改了类图中Iterator
(IEnumerator)的定义。]
using System;
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public class MyCollection : IEnumerable
{
private Node head;
private Node current;
public MyCollection(){
head = current = new Node(null, null);
}
public void Add(object data) {
Node node = current;
while (node.Next != null) {
node = node.Next;
}
Node newNode = new Node(data, null);
node.Next = newNode;
}
public void Remove(object data) {
if (data == null) {
return;
}
Node node = head;
Node previousNode = node;
while (node != null) {
if (object.ReferenceEquals(data, node.Data)) {
previousNode.Next = node.Next;
}
previousNode = node;
node = node.Next;
}
}
public void Insert(int index, object data) {
int currentIndex = -1;
Node node = head;
Node previousNode = node;
while (node != null) {
previousNode = node;
node = node.Next;
currentIndex++;
if (currentIndex == index) {
Node newNode = new Node(data, node);
previousNode.Next = newNode;
break;
}
}
if (node == null || index < 0) {
throw new ArgumentOutOfRangeException("index", "index out of range");
}
}
public void Clear() {
head.Next = null;
current = head;
}
public IEnumerator GetEnumerator() {
return new MyEnumerator(this);
}
#region Nested types
private class Node
{
public object Data;
public Node Next;
public Node(object data, Node next) {
this.Data = data;
this.Next = next;
}
public override string ToString() {
return this.Data.ToString();
}
}
private class MyEnumerator : IEnumerator
{
private MyCollection owner;
public MyEnumerator(MyCollection owner) {
this.owner = owner;
}
#region IEnumerator Members
public object Current {
get {
return owner.current;
}
}
public bool MoveNext() {
owner.current = owner.current.Next;
return (owner.current != null);
}
public void Reset() {
owner.current = owner.head;
}
#endregion
}
#endregion
}
// 客户代码,它不需要知道MyCollection 的内部结构
internal static class Program
{
private static void Main() {
MyCollection myCollection = new MyCollection();
for (int i = 0; i < 10; i++) {
myCollection.Add(i + 1);
}
IEnumerator enumerator = myCollection.GetEnumerator();
enumerator.Reset();
while (enumerator.MoveNext()) {
Console.Write("{0} ", enumerator.Current);
}
}
}
C# 的foreach 关键字实际上是对实现了迭代器模式的集合对象的一种简化遍历写法,因此在
foreach 内部也不要改变集合的元素(对于引用类型的数据,可以改变Item的属性,但不可
将其从集合移除或者往集合中增加Item,对于值类型的数据,改变其Item的属性将不会影响集合内的元素,
即更改无效。)
C#要将一种类型实现为可以迭代访问(即使用foreach 遍历),须实现System.Collections.IEnurable
接口或者实现System.Collections.Generic.IEnumerable<T>(泛型版本)接口。
在实现这些接口过程中,C#2.0 又提供了yield(yield return 和yiled break) 关键字进一步简化
代码,但在IL代码层没有做任何更改,只是编译器为开发人员做掉了一部分工作。
参考:李建忠Webcast讲座: C#面向对象设计模式纵横谈(16):(行为型模式) Interpreter 解释器模式