.Net中的设计模式——Iterator模式

 

一、模式概述

在面向对象设计时,我们常常需要辨认对象的职责。理想的状态下,我们希望自己建立的对象只具有一个职责。对象的责任越少,则该对象的稳定性就越好,受到的约束也就越少。职责分离,可以最大限度地减少彼此之间的耦合程度,从而建立一个松散耦合的对象网络。

职责分离的要点是对被分离的职责进行封装,并以抽象的方式建立起彼此之间的关系。在C#中,我们往往将这些可能变化的对象抽象为接口和抽象类,从而将原来的具体依赖改变为抽象依赖。对象不再受制于具体的实现细节,这就代表他们是可被替换的。

要在设计上做到这一点,首先就要学会分辨职责,学会分辨哪些职责是对象中可变的。以集合对象为例,集合是一个管理和组织数据对象的数据结构。这就表明集合首先应具备一个基本属性,就是集合能够存储数据。这其中包含存储数据的类型、存储空间的大小、存储空间的分配、以及存储的方式和顺序。不具备这些特点,则该对象就不成其为集合对象。也就是说,上述这些属性是集合对象与身俱来的,是其密不可分的职责。然而,集合对象除了能够存储数据外,还必须提供访问其内部数据的行为方式,这是一种遍历机制。同时这种遍历方式,或会根据不同的情形提供不同的实现,如顺序遍历,逆序遍历,或是二叉树结构的中序、前序、后序遍历。

现在我们已经分辨出集合对象拥有的两个职责:一是存储内部数据;二是遍历内部数据。从依赖性来看,前者为集合对象的根本属性,属于一生俱生,一亡俱亡的关系;而后者既是可变化的,又是可分离的。因此,我们将遍历行为分离出来,抽象为一个迭代器,专门提供遍历集合内部数据对象的行为。这就是Iterator模式的本质。

如一个列表对象List,它提供了遍历列表各元素的能力,这种遍历的行为可能包含两种:顺序和逆序遍历。对于一般的List对象而言,采用顺序遍历的方式;而对于特定的List对象,如ReverseList,则按照逆序遍历的方式访问其内部数据。如果不将存储数据和访问数据的职责分离,为实现ReverseList类,就需要重写其父类List中所有用于遍历数据的方法。现在我们采用Iterator模式,在List对象中分离出迭代器IListIterator,那就只需要为这个继承自List对象的ReverseList对象,提供逆序迭代器ReverseListIterator就可以了,如下面的类图所示:

iterator1.GIF
代码如下:

public interface IListIterator

{

void First();

void Last();

bool MoveNext();

}

public class SequenceListIterator:IListIterator

{

private List list = null;

private int index;

public SequenceListIterator(List list)

{

    index = -1;

    this.list = list;

}

public void First()

{

    index = 0;

}

public void Last()

{

    index = list.Count;

}

public bool MoveNext()

{

    index ++;

    return list.Count > index;

}

}

public class ReverseListIterator:IListIterator

{

private List list = null;

private int index;

public ReverseListIterator(List list)

{

    index = list.Count;

    this.list = list;

}

public void First()

{

    index = list.Count - 1;

}

public void Last()

{

    index = 0;

}

public bool MoveNext()

{

    index --;

    return index >= 0;

}

}

public class List

{

public virtual IListIterator CreateIterator()

{

    return new SequenceListIterator(this);

}

}

public class ReverseList:List

{

public override IListIterator CreateIterator()

{

    return new ReverseListIterator(this);

}

}

我们看到,List类通过方法CreateIterator(),创建了SequenceListIterator对象,使得List集合对象能够用顺序迭代的方式遍历内部元素。而要使ReverseList采用逆序的方式遍历其内部元素,则只需重写父类ListCreateIterator()方法,通过创建ReverseListIterator对象,来建立集合与具体迭代器之间的关系。

二、            .Net中的Iterator模式

.Net中,IEnumerator接口就扮演了Iterator模式中迭代器的角色。IEnumerator的定义如下:

public interface IEnumerator

{

    bool MoveNext();

    Object Current {get; }

    void Reset();

}

该接口提供了遍历集合元素的方法,其中主要的方法是MoveNext()。它将集合中的元素下标移到下一个元素。如果集合中没有元素,或已经移到了最后一个,则返回false。能够提供元素遍历的集合对象,在.Net中都实现了IEnumerator接口。

我们在使用.Net集合对象时,会发现一个方法GetEnumerator()方法,它类似前面提到的List对象的CreateIterator()方法。该方法返回的类型是IEnumerator,其内部则是创建并返回一个具体的迭代器对象。正是通过这个方法,建立了具体的集合对象与迭代器之间的关系。

与通常的Iterator模式实现不同,.Net FrameworkGetEnumerator()方法单独抽象出来,定义了接口IEnumerable

public interface IEnumerable

{

    IEnumerator GetEnumerator();

}

IEnumerable接口就像迭代功能的标识,如果集合对象需要具备迭代遍历的功能,就必须实现该接口,并在具体实现中,创建与自身有关系的具体迭代器对象。而要获得该集合对象对应的迭代器,就可以通过该方法,如:

ArrayList al = new ArrayList();

IEnumerator iterator = al.GetEnumerator();

下面,我就以ArrayList对象为例,讨论一下.NetIterator模式的实现方式。首先,我们来看看ArrayList类的定义:

public class ArrayList : IList, ICloneable

它分别实现了IListICloneable接口。其中,ICloneable接口提供了Clone方法,与本文讨论的内容无关,略过不提。IList接口则提供了和链表相关的操作,如AddRemove等。这也是ArrayListArray的不同之处。而IList接口又实现了ICollection接口。

public interface IList : ICollection

ICollection接口是所有集合类型的公共接口,它提供了获得集合长度和同步处理的一些方法,不过在这里,我们需要注意的是它实现了IEnumerable接口:

public interface ICollection : IEnumerable

追本溯源,ArrayList类型间接地实现了IEnumerable接口。在ArrayList中,IEnumerable接口的GetEnumerator()方法实现代码如下:

public virtual IEnumerator GetEnumerator() {

    return new ArrayListEnumeratorSimple(this);

}

GetEnumerator()方法是一个虚方法,这说明,我们可以自定义一个集合类型继承ArrayList,重写这个方法,创建和返回不同的IEnumerator对象,从而实现不同的遍历方式。

为了实现ArrayList的遍历功能,采用的Iterator模式结构如下图所示:

iterator2.GIF
其中,类
ArrayListEnumeratorSimple的实现如下所示:

[Serializable]

private class ArrayListEnumeratorSimple : IEnumerator, ICloneable

{

      // Methods

      internal ArrayListEnumeratorSimple(ArrayList list)

          {

               this.list = list;

                   this.index = -1;

               this.version = list._version;

               this.currentElement = list;

          }

          public object Clone(){//实现略}

          public virtual bool MoveNext()

          {

               if (this.version != this.list._version)

               {

                    throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));

              }

               if (this.index < (this.list.Count - 1))

               {

                      this.index++;

                      this.currentElement = this.list[this.index];

                          return true;

               }

               this.currentElement = this.list;

               this.index = this.list.Count;

               return false;

         }

         public virtual void Reset()

         {

               if (this.version != this.list._version)

               {

                      throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));

               }

               this.currentElement = this.list;

               this.index = -1;

         }

     // Properties

         public virtual object Current

         {

               get

               {

                      object obj1 = this.currentElement;

                      if (obj1 != this.list)

                      {

                         return obj1;

                      }

                      if (this.index == -1)

                      {

                         throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumNotStarted"));

                      }

                      throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumEnded"));

               }

            }

      // Fields

      private object currentElement;

      private int index;

      private ArrayList list;

      private int version;

}

ArrayListEnumeratorSimple实现了IEnumerator接口,实现了MoveNext()CurrentReset()方法或属性。该类是一个私有类型,其构造函数则被internal修饰符限制。在自定义的构造函数中,传入的参数类型是ArrayList。正是通过构造函数传递需要遍历的ArrayList对象,来完成MoveNext()CurrentReset()等操作。

下面,我们来看看如何通过IEnumerator来实现对ArrayList的遍历操作。

using System;

using System.Collections;

using NUnit.Framework

[TestFixture]

public class Tester

{

[Test]

public void TestArrayList()

{

           ArrayList al = new ArrayList();

          al.Add(5);

          al.Add(“Test”);

          IEnumerator e = al.GetEnumerator();

          e.MoveNext();

          Assert.AreEqual(5,e.Current);

          e.MoveNext();

          Assert.AreEqual(“Test”,e.Current);

}

}

而要遍历ArrayList内部所有元素,方法也很简单:

while (e.MoveNext())

{

      Console.WriteLine(e.Current.ToString());

}

事实上,为了用户更方便地遍历集合对象的所有元素,在C#中提供了foreach语句。该语句的实质正是通过IEnumeratorMoveNext()方法来完成遍历的。下面的语句与刚才那段代码是等价的:

foreach (object o in al)

{

    Console.WriteLine(o.ToString());

}

为了验证foreach语句与迭代器的关系,我们来自定义一个ReverseArrayList类。要求遍历这个类的内部元素时,访问顺序是逆序的。要定义这样的一个类,很简单,只需要继承ArrayList类,并重写GetEnumerator()方法既可。

public class ReverseArrayList:ArrayList

{

    public override IEnumerator GetEnumerator()

    {

         return new ReverseArrayListEnumerator(this);

    }

}

其中,类ReverseArrayListEnumerator,实现了接口IEnumerator,它提供了逆序遍历的迭代器:

    public class ReverseArrayListEnumerator:IEnumerator

    {

         public ReverseArrayListEnumerator(ArrayList list)

         {

             this.list = list;

             this.index = list.Count;          

             this.currentElement = list;

         }

         #region IEnumerator Members

         public virtual void Reset()

         {           

             this.currentElement = this.list;

             this.index = this.list.Count;

         }

         public virtual object Current

         {

             get

             {

                  object obj1 = this.currentElement;

                  if (obj1 != this.list)

                  {

                      return obj1;

                  }

                  if (this.index == -1)

                  {

                      throw new InvalidOperationException("Out of the Collection");

                  }

                  throw new InvalidOperationException("Out of the Collection");

             }

         }

         public virtual bool MoveNext()

         {           

             if (this.index > 0)

             {

                  this.index--;

                  this.currentElement = this.list[this.index];

                  return true;

             }

             this.currentElement = this.list;

             this.index = 0;

             return false;

         }

         #endregion

         private object currentElement;

         private int index;

         private ArrayList list;

    }

注意ReverseArrayListEnumerator类与前面的ArrayListEnumeratorSimple类的区别,主要在于遍历下一个元素的顺序。ReverseArrayListEnumerator中的MoveNext()方法,将下标往前移动,以保证元素遍历的逆序。同时在构造函数初始化时,将整个ArrayList对象的元素个数赋予下标的初始值:

this.index = list.Count;

我们来比较一下ArrayListReversieArrayList类之间,通过foreach遍历后的结果。

         [STAThread]

         public static void Main(string[] args)

         {

             ArrayList al = new ArrayList();

             al.Add(1);

             al.Add(2);

             al.Add(3);

             ReverseArrayList ral = new ReverseArrayList();

             ral.Add(1);

             ral.Add(2);

             ral.Add(3);

             Console.WriteLine("The Sequence ArrayList:");

             foreach (int i in al)

             {

                  Console.Write("{0} ",i);

             }

             Console.WriteLine();

             Console.WriteLine("The Reverse ArrayList:");            

             foreach (int i in ral)

             {

                  Console.Write("{0} ",i);

             }

             Console.ReadLine();

         }

我们分别将数字123以同样的顺序添加到ArrayListReverseArrayList对象中,然后再通过foreach语句遍历输出其内部元素。运行后,很明显可以看到遍历ArrayList对象al,其顺序为123;而ReverseArrayList则为321

iterator3.GIF
由于我们应用
Iterator模式,将迭代器与集合对象完全分离,所以,即便我们完全修改了ReverseArrayList的遍历方式,实现ReverseArrayList也是非常容易的,同时它并没有影响到集合对象本身存储数据对象的职能。

posted @ 2007-12-26 12:08  啸翱姜糊  阅读(209)  评论(0编辑  收藏  举报