設計模式的征途—21.迭代器(Iterator)模式
我們都用過電視機遙控器,通過它我們可以進行開機、關機、換臺、改變音量等操作。我們可以將電視機看做一個存儲電視頻道的集合對象,通過遙控器可以對電視機中的頻道集合進行操作,例如返回上一個頻道、跳轉到下一個頻道或者跳轉到指定的頻道等。遙控器的出現,使得用戶不需要知道這些頻道到底如何存儲在電視機中。在軟件開發中也存在類似于電視機一樣的類,他們可以存儲了多個成員對象(元素),這些類通常稱為聚合類(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()方法。)
參考資料

(1)劉偉,《設計模式的藝術—軟件開發人員內功修煉之道》
(2)圣杰,《C#設計模式之迭代器模式》

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



浙公網安備 33010602011771號