前言
迭代器模式,属于对象行为型模式。它的目的是将一个集合对象的迭代与其本身分离,使这个聚合对象更单纯,并且在遍历的同时不需要暴露该聚合对象的内部结构。
在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露对象的内部表示 ”。
结构
- Aggregate(聚合对象接口):负责定义创建相应迭代器对象的接口;
- ConcreteAggregate(具体聚合对象):具体的聚合对象,实现具体迭代器对象的创建;
- Iterator(迭代器接口):定义对聚合对象的访问和遍历的接口;
- ConcreteIterator(具体迭代器):实现对聚合对象的访问和遍历,并记录当前遍历的位置;
示例
public interface Iterator
{
object Current { get; }
bool Next();
void Reset();
}
public class ConcreteIterator : Iterator
{
private int Index { set; get; } = -1;
private int[] IntArr;
public ConcreteIterator(int[] intArr)
{
this.IntArr = intArr;
}
public object Current
{
get
{
if(this.IntArr == null
|| this.IntArr.Length < 1
|| this.Index < 0
|| this.Index >= this.IntArr.Length)
{
return null;
}
return this.IntArr[this.Index];
}
}
public bool Next()
{
this.Index++;
return this.Index < this.IntArr.Length;
}
public void Reset()
{
this.Index = -1;
}
}
public interface Aggregate
{
Iterator CreateIterator();
}
public class ConcreteAggregate : Aggregate
{
private int[] IntArr;
public ConcreteAggregate()
{
this.IntArr = new int[]{1, 2, 3, 4, 5, 6, 7};
}
public Iterator CreateIterator()
{
return new ConcreteIterator(this.IntArr);
}
}
static void Main(string[] args)
{
Aggregate agg = new ConcreteAggregate();
Iterator.Iterator iterator = agg.CreateIterator();
while (iterator.Next())
{
Console.WriteLine(iterator.Current.ToString());
}
Console.ReadKey();
}
在上述示例中,由ConcreteAggregate类提供了一个工厂方法(Factory Method)来返回它的迭代器。迭代器的公共接口Iterator保证了各个迭代器的一致性,当我们需要变更ConcreteAggregate的迭代操作时,只需要增加一个迭代器并修改CreateIterator函数中返回的迭代器即可。迭代器的存在使聚合的内部结构对调用者透明,同时又统一了各个聚合结构的外部迭代方式(无论那种聚合结构,调用者只需要使用Next函数和Current属性即可完成迭代)。
迭代器在C#中的应用
我们在使用C#语言开发时经常会使用关键字foreach来遍历一个集合。一个类型若要支持使用foreach来遍历,需要满足以下两点:
-
- 该类型具有公共无参数的方法GetEnumerator,并且其返回值类型为类、接口或结构;
- 函数GetEnumerator的返回值类型中必须包含公共属性Current和公共无参且返回值为bool类型的函数MoveNext;
foreach语法可参照:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/foreach-in
在使用foreach遍历一个类型时,首先会调用这个类型的GetEnumerator函数来获得一个对象,之后调用这个对象的MoveNext函数,该函数返回true则进入循环内并将属性Current的值赋给foreach中定义的变量,反之则跳出循环。可参照如下代码。
public class ClassA
{
public int[] IntArr { set; get; } = new int[] { 1, 2, 3, 4, 5, 6, 7 };
public ClassB GetEnumerator()
{
Console.WriteLine("ClassA.GetEnumerator");
Console.WriteLine("------------------------");
return new ClassB(this);
}
}
public class ClassB
{
private int Index { set; get; } = -1;
private ClassA ClassA { set; get; }
public ClassB(ClassA classA)
{
this.ClassA = classA;
}
public int Current
{
get
{
Console.WriteLine($"[ClassB.Current]:{this.ClassA.IntArr[this.Index]}");
return this.ClassA.IntArr[this.Index];
}
}
public bool MoveNext()
{
this.Index++;
Console.WriteLine($"[ClassB.MoveNext]:{this.Index < this.ClassA.IntArr.Length}");
return this.Index < this.ClassA.IntArr.Length;
}
}
static void Main(string[] args)
{
ClassA classA = new ClassA();
foreach (var item in classA)
{
Console.WriteLine($"-------{item.ToString()}");
}
Console.ReadKey();
}
ClassA类中的函数GetEnumerator所返回的实际上就是一个迭代器,通过这个迭代器中的函数MoveNext与属性Current来遍历集合。效果与如下代码相同。
static void Main(string[] args)
{
ClassB classB = new ClassB(new ClassA());
while (classB.MoveNext())
{
Console.WriteLine($"-------{classB.Current.ToString()}");
}
Console.ReadKey();
}
终上所述,关键字foreach实际上是一个方便我们使用迭代器模式去遍历一个对象的语法糖,也就意味着所有支持foreach的类型都采用了迭代器模式。而C#也为我们提供了迭代器模式中的聚合对象接口IEnumerable以及迭代器接口IEnumerator。在接口IEnumerable中只定义了一个返回IEnumerator的函数GetEnumerator,而在Ienumerator接口中则定义了属性Currnet、MoveNext和Reset函数。
public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
{
...
...
public Enumerator GetEnumerator() {
return new Enumerator(this);
}
/// <internalonly/>
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return new Enumerator(this);
}
...
...
...
}
public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
internal Enumerator(List<T> list) {
this.list = list;
index = 0;
version = list._version;
current = default(T);
}
public void Dispose() {
}
public bool MoveNext() {
List<T> localList = list;
if (version == localList._version && ((uint)index < (uint)localList._size))
{
current = localList._items[index];
index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1;
current = default(T);
return false;
}
public T Current {
get {
return current;
}
}
Object System.Collections.IEnumerator.Current {
get {
if( index == 0 || index == list._size + 1) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return Current;
}
}
void System.Collections.IEnumerator.Reset() {
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
current = default(T);
}
}
以上为.NET Framework 4.6.2的部分List类源码,有兴趣可以自行下载(https://referencesource.microsoft.com/)
总结
迭代器模式能够帮助我们将集合类型的迭代逻辑与类型本身分离,使得该类型的底层结构对调用者透明,并且能够使调用者使用相同的方式(MoveNext和Current)去遍历不同的集合类型(List、数组)或使用同一集合类型以及同一方式去执行不同的迭代逻辑。但迭代器的个数随着迭代逻辑的增加而增加,迭代器的数量越多,系统的复杂性越高。
需要注意的是,无论是否使用迭代器模式,都不能在遍历的过程中对该集合进行增加或删除操作(在.NET源码中采用版本号_version来判断是否在遍历的过程中操作过该集合)。
以上,就是我对迭代器模式的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078501.html)