二十三种设计模式[16]

前言

       迭代器模式,属于对象行为型模式。它的目的是将一个集合对象的迭代与其本身分离,使这个聚合对象更单纯,并且在遍历的同时不需要暴露该聚合对象的内部结构。

       在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露对象的内部表示 ”。

结构

Interator_1

  • Aggregate(聚合对象接口):负责定义创建相应迭代器对象的接口;
  • ConcreteAggregate(具体聚合对象):具体的聚合对象,实现具体迭代器对象的创建;
  • Iterator(迭代器接口):定义对聚合对象的访问和遍历的接口;
  • ConcreteIterator(具体迭代器):实现对聚合对象的访问和遍历,并记录当前遍历的位置;

示例

Interator_2

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来遍历,需要满足以下两点:

    1. 该类型具有公共无参数的方法GetEnumerator,并且其返回值类型为类、接口或结构;
    2. 函数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();
}

image

       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)

原文地址:https://www.cnblogs.com/wxingchen/p/10078501.html