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

          {

               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

         {

               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也是非常容易的,同時它並沒有影響到集合對象本身存儲數據對象的職能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章