二十三种设计模式[16] - 迭代器模式(Iterator Pattern)
前言
迭代器模式,属于对象行为型模式。它的目的是将一个集合对象的迭代与其本身分离,使这个聚合对象更单纯,并且在遍历的同时不需要暴露该聚合对象的内部结构。
在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露对象的内部表示 ”。
结构
- 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)