分享

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

 ljjzlm 2006-08-04

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

Filed under: .Net Framework, Design & Pattern — bruce zhang @ 1:43 pm

一、模式概述

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

职责分离的要点是对被分离的职责进行封装,并以抽象的方式建立起彼此之间的关系。在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采用逆序的方式遍历其内部元素,则只需重写父类List的CreateIterator()方法,通过创建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 Framework将GetEnumerator()方法单独抽象出来,定义了接口IEnumerable:
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

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

下面,我就以ArrayList对象为例,讨论一下.Net中Iterator模式的实现方式。首先,我们来看看ArrayList类的定义:
public class ArrayList : IList, ICloneable

它分别实现了IList和ICloneable接口。其中,ICloneable接口提供了Clone方法,与本文讨论的内容无关,略过不提。IList接口则提供了和链表相关的操作,如Add,Remove等。这也是ArrayList和Array的不同之处。而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()、Current、Reset()方法或属性。该类是一个私有类型,其构造函数则被internal修饰符限制。在自定义的构造函数中,传入的参数类型是ArrayList。正是通过构造函数传递需要遍历的ArrayList对象,来完成MoveNext()、Current、Reset()等操作。

下面,我们来看看如何通过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语句。该语句的实质正是通过IEnumerator的MoveNext()方法来完成遍历的。下面的语句与刚才那段代码是等价的:
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;

我们来比较一下ArrayList和ReversieArrayList类之间,通过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();
         }

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

iterator3.GIF

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多