天天看點

.Net中的設計模式——Iterator模式

一、模式概述

在面向對象設計時,我們常常需要辨認對象的職責。理想的狀态下,我們希望自己建立的對象隻具有一個職責。對象的責任越少,則該對象的穩定性就越好,受到的限制也就越少。職責分離,可以最大限度地減少彼此之間的耦合程度,進而建立一個松散耦合的對象網絡。

職責分離的要點是對被分離的職責進行封裝,并以抽象的方式建立起彼此之間的關系。在C#中,我們往往将這些可能變化的對象抽象為接口和抽象類,進而将原來的具體依賴改變為抽象依賴。對象不再受制于具體的實作細節,這就代表他們是可被替換的。

要在設計上做到這一點,首先就要學會分辨職責,學會分辨哪些職責是對象中可變的。以集合對象為例,集合是一個管理群組織資料對象的資料結構。這就表明集合首先應具備一個基本屬性,就是集合能夠存儲資料。這其中包含存儲資料的類型、存儲空間的大小、存儲空間的配置設定、以及存儲的方式和順序。不具備這些特點,則該對象就不成其為集合對象。也就是說,上述這些屬性是集合對象與身俱來的,是其密不可分的職責。然而,集合對象除了能夠存儲資料外,還必須提供通路其内部資料的行為方式,這是一種周遊機制。同時這種周遊方式,或會根據不同的情形提供不同的實作,如順序周遊,逆序周遊,或是二叉樹結構的中序、前序、後序周遊。

現在我們已經分辨出集合對象擁有的兩個職責:一是存儲内部資料;二是周遊内部資料。從依賴性來看,前者為集合對象的根本屬性,屬于一生俱生,一亡俱亡的關系;而後者既是可變化的,又是可分離的。是以,我們将周遊行為分離出來,抽象為一個疊代器,專門提供周遊集合内部資料對象的行為。這就是Iterator模式的本質。

如一個清單對象List,它提供了周遊清單各元素的能力,這種周遊的行為可能包含兩種:順序和逆序周遊。對于一般的List對象而言,采用順序周遊的方式;而對于特定的List對象,如ReverseList,則按照逆序周遊的方式通路其内部資料。如果不将存儲資料和通路資料的職責分離,為實作ReverseList類,就需要重寫其父類List中所有用于周遊資料的方法。現在我們采用Iterator模式,在List對象中分離出疊代器IListIterator,那就隻需要為這個繼承自List對象的ReverseList對象,提供逆序疊代器ReverseListIterator就可以了,如下面的類圖所示:

.Net中的設計模式——Iterator模式

代碼如下:

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

 public ReverseListIterator(List list)

      index = list.Count - 1;

      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模式結構如下圖所示:

.Net中的設計模式——Iterator模式

其中,類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()

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

         // 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);

           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

         {           

             this.currentElement = this.list;

             this.index = this.list.Count;

             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");

             }

         {          

             if (this.index > 0)

                  this.index–;

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

                  return true;

             this.index = 0;

             return false;

         #endregion

注意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.ReadLine();