1. 程式人生 > >第17章 迭代器模式(Iterator Pattern)

第17章 迭代器模式(Iterator Pattern)

概述

在面向物件的軟體設計中,我們經常會遇到一類集合物件,這類集合物件的內部結構可能有著各種各樣的實現,但是歸結起來,無非有兩點是需要我們去關心的:一是集合內部的資料儲存結構,二是遍歷集合內部的資料。面向物件設計原則中有一條是類的單一職責原則,所以我們要儘可能的去分解這些職責,用不同的類去承擔不同的職責。Iterator模式就是分離了集合物件的遍歷行為,抽象出一個迭代器類來負責,這樣既可以做到不暴露集合的內部結構,又可讓外部程式碼透明的訪問集合內部的資料。

意圖

提供一種方法順序訪問一個聚合物件中各個元素, 而又不需暴露該物件的內部表示。[GOF 《設計模式》]

結構圖

Iterator模式結構圖如下:

圖1  Iterator模式結構圖

生活中的例子

迭代器提供一種方法順序訪問一個集合物件中各個元素,而又不需要暴露該物件的內部表示。在早期的電視機中,一個撥盤用來改變頻道。當改變頻道時,需要手工轉動撥盤移過每一個頻道,而不論這個頻道是否有訊號。現在的電視機,使用[後一個]和[前一個]按鈕。當按下[後一個]按鈕時,將切換到下一個預置的頻道。想象一下在陌生的城市中的旅店中看電視。當改變頻道時,重要的不是幾頻道,而是節目內容。如果對一個頻道的節目不感興趣,那麼可以換下一個頻道,而不需要知道它是幾頻道。

圖2  使用選頻器做例子的Iterator模式物件圖

Iterator模式解說

在面向物件的軟體設計中,我們經常會遇到一類集合物件,這類集合物件的內部結構可能有著各種各樣的實現,但是歸結起來,無非有兩點是需要我們去關心的:一是集合內部的資料儲存結構,二是遍歷集合內部的資料。面向物件設計原則中有一條是類的單一職責原則,所以我們要儘可能的去分解這些職責,用不同的類去承擔不同的職責。Iterator模式就是分離了集合物件的遍歷行為,抽象出一個迭代器類來負責,這樣既可以做到不暴露集合的內部結構,又可讓外部程式碼透明的訪問集合內部的資料。下面看一個簡單的示意性例子,類結構圖如下:

圖3 示例程式碼結構圖

首先有一個抽象的聚集,所謂的聚集就是就是資料的集合,可以迴圈去訪問它。它只有一個方法GetIterator()讓子類去實現,用來獲得一個迭代器物件。

/**//// <summary>

/// 抽象聚集

/// </summary>

public interface IList

{
    IIterator GetIterator();
}

抽象的迭代器,它是用來訪問聚集的類,封裝了一些方法,用來把聚集中的資料按順序讀取出來。通常會有MoveNext()、CurrentItem()、Fisrt()、Next()等幾個方法讓子類去實現。

/**//// <summary>

/// 抽象迭代器

/// </summary>

public interface IIterator
{
    bool MoveNext();

    Object CurrentItem();

    void First();

    void Next();
}

具體的聚集,它實現了抽象聚集中的唯一的方法,同時在裡面儲存了一組資料,這裡我們加上Length屬性和GetElement()方法是為了便於訪問聚集中的資料。

/**//// <summary>

/// 具體聚集

/// </summary>

public class ConcreteList : IList
{
    int[] list;

    public ConcreteList()

    {
        list = new int[] { 1,2,3,4,5};
    }

    public IIterator GetIterator()

    {
        return new ConcreteIterator(this);
    }

    public int Length

    {
        get { return list.Length; }
    }

    public int GetElement(int index)

    {
        return list[index];
    }
}

具體迭代器,實現了抽象迭代器中的四個方法,在它的建構函式中需要接受一個具體聚集型別的引數,在這裡面我們可以根據實際的情況去編寫不同的迭代方式。

/**//// <summary>

/// 具體迭代器

/// </summary>

public class ConcreteIterator : IIterator

{
    private ConcreteList list;

    private int index;

    public ConcreteIterator(ConcreteList list)

    {
        this.list = list;

        index = 0;
    }

    public bool MoveNext()

    {
        if (index < list.Length)

            return true;

        else

            return false;
    }

    public Object CurrentItem()

    {
        return list.GetElement(index) ;
    }

    public void First()

    {
        index = 0;
    }

    public void Next()

    {
        if (index < list.Length)

        {
            index++;
        }
    }
}

簡單的客戶端程式呼叫:

/**//// <summary>

/// 客戶端程式

/// </summary>

class Program

{
    static void Main(string[] args)

    {
        IIterator iterator;

        IList list = new ConcreteList();

        iterator = list.GetIterator();

        while (iterator.MoveNext())

        {
            int i = (int)iterator.CurrentItem();
            Console.WriteLine(i.ToString());

            iterator.Next();
        }

        Console.Read();

    }

}

一個簡單的迭代器示例就結束了,這裡我們並沒有利用任何的.NET特性,在C#中,實現Iterator模式已經不需要這麼麻煩了,已經C#語言本身就有一些特定的實現,下面會說到。

.NET中的Iterator模式

在.NET下實現Iterator模式,對於聚集介面和迭代器介面已經存在了,其中IEnumerator扮演的就是迭代器的角色,它的實現如下:

public interface IEumerator

{
    object Current
    {
        get;
    }

    bool MoveNext();

    void Reset();

}

屬性Current返回當前集合中的元素,Reset()方法恢復初始化指向的位置,MoveNext()方法返回值true表示迭代器成功前進到集合中的下一個元素,返回值false表示已經位於集合的末尾。能夠提供元素遍歷的集合物件,在.Net中都實現了IEnumerator介面。

IEnumerable則扮演的就是抽象聚集的角色,只有一個GetEnumerator()方法,如果集合物件需要具備跌代遍歷的功能,就必須實現該介面。

public interface IEnumerable

{
    IEumerator GetEnumerator();
}

下面看一個在.NET1.1下的迭代器例子,Person類是一個可列舉的類。PersonsEnumerator類是一個列舉器類。這個例子來自於http://www.theserverside.net/,被我簡單的改造了一下。

public class Persons : IEnumerable 


    public string[] m_Names; 

    public Persons(params string[] Names) 
    
        m_Names = new string[Names.Length]; 

        Names.CopyTo(m_Names,0); 
    } 

    private string this[int index] 
    
        get 
        
            return m_Names[index]; 
        } 

        set 
        
            m_Names[index] = value; 
        } 
    }

    public IEnumerator GetEnumerator()
    {
        return new PersonsEnumerator(this);
    }
}


public class PersonsEnumerator : IEnumerator
{
    private int index = -1;

    private Persons P;

    public PersonsEnumerator(Persons P)
    {
        this.P = P;
    }

    public bool MoveNext()
    {
        index++;

        return index < P.m_Names.Length;
    }

    public void Reset()
    {
        index = -1;
    }

    public object Current
    {
        get

        {
            return P.m_Names[index];
        }
    }

來看客戶端程式碼的呼叫:

class Program 

    static void Main(string[] args) 
    
        Persons arrPersons = new Persons("Michel","Christine","Mathieu","Julien"); 

        foreach (string s in arrPersons) 

        
            Console.WriteLine(s); 
        }

        Console.ReadLine(); 
    } 
}

程式將輸出:

Michel 

Christine 

Mathieu 

Julien

現在我們分析編譯器在執行foreach語句時到底做了什麼,它執行的程式碼大致如下:

class Program 


    static void Main(string[] args) 
    
          Persons arrPersons = new Persons("Michel","Christine","Mathieu","Julien"); 

          IEnumerator e = arrPersons.GetEnumerator(); 

          while (e.MoveNext()) 
          
            Console.WriteLine((string)e.Current); 

          }

          Console.ReadLine();
    } 
}

可以看到這段程式碼跟我們最前面提到的示例程式碼非常的相似。同時在這個例子中,我們把大部分的精力都花在了實現迭代器和可迭代的類上面,在.NET2.0下面,由於有了yield return關鍵字,實現起來將更加的簡單優雅。下面我們把剛才的例子在2.0下重新實現一遍:

public class Persons : IEnumerable 

    string[] m_Names;