设计模式的征途—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#設計模式之迭代器模式》
?
作者:周旭龍
出處:http://edisonchou.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。
轉載于:https://www.cnblogs.com/edisonchou/p/7442138.html
總結
以上是生活随笔為你收集整理的设计模式的征途—21.迭代器(Iterator)模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AJAX跨域资源共享 CORS 详解
- 下一篇: 设置element表格透明样式