設計模式的征途—21.迭代器(Iterator)模式

Edison Chou發表於2017-09-03

我們都用過電視機遙控器,通過它我們可以進行開機、關機、換臺、改變音量等操作。我們可以將電視機看做一個儲存電視訊道的集合物件,通過遙控器可以對電視機中的頻道集合進行操作,例如返回上一個頻道、跳轉到下一個頻道或者跳轉到指定的頻道等。遙控器的出現,使得使用者不需要知道這些頻道到底如何儲存在電視機中。在軟體開發中也存在類似於電視機一樣的類,他們可以儲存了多個成員物件(元素),這些類通常稱為聚合類(Aggregate Class),對應的物件稱為聚合物件。為了更加方便地操作這些聚合物件,同時可以很靈活地為聚合物件增加不同的遍歷方法,也需要類似於電視機遙控器一樣的角色,可以訪問一個聚合物件中的元素擔憂部需要暴露它的內部結構,這就是我們需要學習的迭代器模式。

迭代器模式(Iterator) 學習難度:★★★☆☆ 使用頻率:★★★★★

一、銷售管理系統中資料的遍歷

Background : M公司為某商場開發了一套銷售管理系統,在對該系統進行分析和設計時,M公司開發人員發現經常需要對系統中的商品資料、客戶資料等進行遍歷,為了複用這些遍歷程式碼,M公司開發人員設計了一個抽象的資料聚合類AbstractObjectList,而將儲存商品和客戶登記的類作為其子類。AbstractObjectList類結構如下圖所示。

在上圖中,IList型別的物件objects用於儲存資料,AbstractObjectList類的方法說明如下表所示:

AbstractObjectList類的子類ProductList和CustomerList分別用於儲存商品資料和客戶資料。

  M公司開發人員通過對AbstractObjectList類結構進行分析,發現該設計方案存在以下問題:

  (1)在該類中,AddObject()與RemoveObject()等方法用於管理資料,而GetNextItem()、GetPreviousItem()、IsFirst()等方法又用於遍歷資料,導致了聚合類的職責過重,違反了單一職責原則。

  (2)如果將抽象聚合類宣告為一個介面,則在這個介面中充斥著大量方法,不利於子類實現,違反了介面隔離原則。

  (3)如果將所有的遍歷操作都交給子類來實現,將導致子類程式碼過於龐大,而且必須暴露AbstractObjectList類的內部儲存細節,向子類公開自己的私有屬性,否則子類無法實施對資料的遍歷,將破壞AbstractObjectList類的封裝性。

  如何解決該問題?解決方案之一就是將聚合類中負責遍歷資料的方法提取出來,封裝到專門的類中,實現資料儲存和資料遍歷的分離無須暴露聚合類的內部屬性即可對其進行操作,這正是迭代器模式的意圖所在。

二、迭代器模式概述

2.1 迭代器模式簡介

  在軟體開發中,經常需要使用聚合物件來儲存一系列資料。聚合物件擁有兩個職責:一是儲存資料,二是遍歷資料。從依賴性來看,前者是聚合物件的基本職責,而後者既是可變化的又是可分離的。因此,可以將遍歷資料的行為從聚合物件中分離出來,封裝在一個被稱為“迭代器”的物件中,由迭代器來提供遍歷聚合物件內部資料的行為,這將簡化聚合物件的設計,更加符合單一職責原則。

迭代器(Iterator)模式:提供一種方法來訪問聚合物件,而不用暴露這個物件的內部表示,其別名為遊標(Cursor)。迭代器模式是一種物件行為型模式。  

2.2 迭代器模式結構

  (1)Iterator(抽象迭代器):定義了訪問和遍歷元素的介面,宣告瞭用於遍歷資料元素的方法。

  (2)ConcreteIterator(具體迭代器):它實現了抽象迭代器介面,完成對聚合物件的遍歷。

  (3)Aggregate(抽象聚合類):用於儲存和管理元素物件,宣告一個CreateIterator()方法用於建立一個迭代器物件,充當抽象迭代器工廠角色。

  (4)ConcreteAggregate(具體聚合類):實現了在抽象聚合類中宣告的CreateIterator()方法,返回一個對應的具體迭代器ConcreteIterator例項。

三、銷售管理系統中資料的遍歷實現

3.1 重構後的設計結構

  其中,AbstractObjectList充當抽象聚合類,ProductList充當具體聚合類,AbstractIterator充當抽象迭代器,ProductIterator充當具體迭代器。

3.2 重構後的程式碼實現

  (1)抽象聚合類:AbstractObjectList

    /// <summary>
    /// 抽象聚合類:AbstractObjectList
    /// </summary>
    public abstract class AbstractObjectList
    {
        protected IList<object> objectList = new List<object>();

        public AbstractObjectList (IList<object> objectList)
        {
            this.objectList = objectList;
        }

        public void AddObject(object obj)
        {
            this.objectList.Add(obj);
        }

        public void RemoveObject(object obj)
        {
            this.objectList.Remove(obj);
        }

        public IList<Object> GetObjectList()
        {
            return this.objectList;
        }

        // 宣告建立迭代器物件的抽象工廠方法
        public abstract AbstractIterator CreateIterator();
    }

  (2)具體聚合類 - ProductList 與 具體迭代器 - ProductIterator => 這裡採用了內部類的方式

    /// <summary>
    /// 具體聚合類:ProductList
    /// </summary>
    public class ProductList : AbstractObjectList
    {
        public ProductList(IList<object> objectList) : base(objectList)
        {
        }

        public override AbstractIterator CreateIterator()
        {
            return new ProductIterator(this);
        }

        /// <summary>
        /// 內部類=>具體迭代器:ProductIterator
        /// </summary>
        private class ProductIterator : AbstractIterator
        {
            private ProductList productList;
            private IList<object> products;
            private int cursor1;    // 定義一個遊標,用於記錄正向遍歷的位置
            private int cursor2;    // 定義一個遊標,用於記錄逆向遍歷的位置

            public ProductIterator(ProductList productList)
            {
                this.productList = productList;
                this.products = productList.GetObjectList();       // 獲取集合物件
                this.cursor1 = 0;                                                            // 設定正向遍歷遊標的初始值
                this.cursor2 = this.products.Count - 1;                 // 設定逆向遍歷遊標的初始值
            }

            public object GetNextItem()
            {
                return products[cursor1];
            }

            public object GetPreviousItem()
            {
                return products[cursor2];
            }

            public bool IsFirst()
            {
                return cursor2 == -1;
            }

            public bool IsLast()
            {
                return cursor1 == products.Count;
            }

            public void Next()
            {
                if (cursor1 < products.Count)
                {
                    cursor1++;
                }
            }

            public void Previous()
            {
                if (cursor2 > -1)
                {
                    cursor2--;
                }
            }
        }
    }

  (3)抽象迭代器:AbstractIterator

    /// <summary>
    /// 抽象迭代器:AbstractIterator
    /// </summary>
    public interface AbstractIterator
    {
        void Next();                                  // 移動至下一個元素
        bool IsLast();                               // 判斷是否為最後一個元素
        void Previous();                        // 移動至上一個元素
        bool IsFirst();                             // 判斷是否為第一個元素
        object GetNextItem();           // 獲取下一個元素
        object GetPreviousItem();   // 獲取上一個元素
    }

  (4)客戶端測試

    public class Program
    {
        public static void Main(string[] args)
        {
            IList<object> products = new List<object>();
            products.Add("倚天劍");
            products.Add("屠龍刀");
            products.Add("斷腸草");
            products.Add("葵花寶典");
            products.Add("四十二章經");

            AbstractObjectList objectList = new ProductList(products);      // 建立聚合物件
            AbstractIterator iterator = objectList.CreateIterator();                // 建立迭代器物件

            Console.WriteLine("正向遍歷");
            while (!iterator.IsLast())
            {
                Console.Write(iterator.GetNextItem() + ",");
                iterator.Next();
            }

            Console.WriteLine();
            Console.WriteLine("-------------------------------------------------------");
            Console.WriteLine("逆向遍歷");
            while (!iterator.IsFirst())
            {
                Console.Write(iterator.GetPreviousItem() + ",");
                iterator.Previous();
            }

            Console.ReadKey();
        }
    }

  F5編譯執行後的結果如下圖所示:

  

四、迭代器模式小結

4.1 主要優點

  (1)支援以不同方式遍歷一個聚合物件,在同一個聚合物件上可以定義多種便利方式。

  (2)增加新的聚合類和迭代器類都很方便 => 無須修改原有程式碼,符合開閉原則。

4.2 主要缺點

  增加新的聚合類需要對應增加新的迭代器類 => 類的個數會成對增加!

4.3 應用場景

  (1)訪問一個聚合物件的內容而無須暴露它的內部表示。

  (2)需要為一個聚合物件提供多種遍歷方式。

  (3)重點 => 該模式在.Net中,可以通過實現IEnumberable介面即可,不再需要單獨實現! (在.NET下,迭代器模式中的聚集介面和迭代器介面都已經存在了,其中IEnumerator介面扮演的就是迭代器角色,IEnumberable介面則扮演的就是抽象聚集的角色,其中定義了GetEnumerator()方法。)

參考資料

  DesignPattern

  (1)劉偉,《設計模式的藝術—軟體開發人員內功修煉之道》

  (2)聖傑,《C#設計模式之迭代器模式》

 

相關文章