[C# 设计模式] Iterator - 迭代器模式:我与一份奥利奥早餐的故事
Iterator - 迭代器模式
?
目錄
- 前言
- 回顧
- UML 類圖
- 代碼分析
- 抽象的 UML 類圖
- 思考
?
前言
這是一包奧利奧(數組),里面藏了很多塊奧利奧餅干(數組中的元素),我將它們放在一個碟子上慢慢排好,從上往下一塊塊的拿起來(迭代),再一口氣吃掉,這就是今天的早餐,也就是要說的?Iterator - 迭代器模式。
?
?
回顧
我們常用的 for 和 foreach,其實就是 MS 給我們封裝后的迭代器模式。為什么數組和集合能夠使用這兩個關鍵字呢?因為他們都實現了一個接口 IEnumerable,實現了內部方法?GetEnumerator。我們對一個集合,或者是數組進行遍歷的同時,也就是數組或集合元素的下標不斷遞增的一個過程。
圖
左邊的下標?0 表示數組的第一個元素;
左邊的下標 1 表示數組的第二個元素;
... ...
左邊的下標 i 表示數組的第i+1個元素;
最后一個元素就是數組的長度 - 1;
?UML 類圖
?圖
圖
?
代碼分析
? IEnumerable 接口
interface IEnumerable{IEnumerator GetEnumerator();}這里只有一個方法?GetEnumerator(),該方法可以生成一個遍歷集合的元素的迭代器。通過該迭代器,就可以進行集合元素的遍歷了。
?
IEnumerator 接口
interface IEnumerator{bool MoveNext();object GetCurrent();}實現該接口的實例可以成為迭代器。這里有兩個方法: MoveNext(),GetCurrent()。
MoveNext():移動到下一個元素的下標,如果存在該下標(沒有超出索引位置),則返回 true。主要用于終止循環條件。
GetCurrent():獲取當前集合元素的值,不過這里因為返回的類型為 object,可能需要進行強轉,當然,你也可以選擇使用泛型。
?
Dish.cs 類(碟子)
class Dish : IEnumerable{private readonly List<Aoliao> _aoliaos;public Dish(){_aoliaos = new List<Aoliao>();}public IEnumerator GetEnumerator(){return new DishIterator(this);}public void AppendAoliao(Aoliao aoliao){_aoliaos.Add(aoliao);}public int GetCount(){return _aoliaos.Count;}public Aoliao GetAoliao(int index){if (index >= GetCount()){throw new IndexOutOfRangeException();}return _aoliaos[index];}}這是一個碟子類,因為它實現了 IEnumerable 接口,我把它當作集合,用于放置拆開包裝后的奧利奧餅干。
這里的構造函數,進行對 List<Aoliao> 進行集合的初始化。
AppendAoliao(Aoliao aoliao):在原有的集合中追加新元素,在放置好的奧利奧餅干后再添加一塊新的奧利奧餅干。
GetCount():獲取集合的個數,獲取碟子上奧利奧餅干的總個數。
GetAoliao(int index):根據下標獲取集合中的元素。
?
? DishIterator.cs 類(碟子迭代器)
class DishIterator : IEnumerator{private int _index;private readonly Dish _dish;public DishIterator(Dish cookie){_index = -1;_dish = cookie;}public bool MoveNext(){_index++;return _index < _dish.GetCount();}public object GetCurrent(){try{return _dish.GetAoliao(_index);}catch (IndexOutOfRangeException){throw new InvalidOperationException();}}}該類實現了 IEnumerator 接口,作為迭代器的一個實例對象,用于遍歷?Dish 對象內集合的一個迭代器對象,這里有兩個字段:
_index:用于指定數組元素的下標,遞增,注意,這里我選擇讓下標從 -1 開始。
_dish:保存對 Dish 類的一個引用。
MoveNext():移動到下一個元素的下標,遞增下標 _index,假如索引超出界限則返回 false,從這里可以得知奧利奧餅干有沒有吃完。
GetCurrent():獲取當前元素,根據下標 _index。
?
Aoliao.cs 類(奧利奧餅干)
class Aoliao{/// <summary>/// 味道/// </summary>public bool Taste { get; set; }}這里的?Taste 屬性,我只用于標識它是否好吃。
Main.cs 類
class Program{static void Main(string[] args){var dish = new Dish();dish.AppendAoliao(new Aoliao() { Taste = true });dish.AppendAoliao(new Aoliao() { Taste = true });dish.AppendAoliao(new Aoliao() { Taste = true });var iterator = dish.GetEnumerator();while (iterator.MoveNext()){var aoliao = (Aoliao)iterator.GetCurrent();Console.WriteLine("味道: " + aoliao.Taste);}Console.Read();}}?
dish 作為一個數組,在一開始初始化的時候放置幾塊奧利奧餅干,通過?GetEnumerator() 可以得到迭代器,在 while 循環中,通過 MoveNext() 可以移動到集合的下一個元素下標,并取出奧利奧餅干,直到超出索引范圍(即奧利奧餅干已經吃完)才會終止循環。這就是之前為什么我將?DishIterator 的下標(_index)初始化值為 -1,MoveNext() 方法會先移動光標的位置,再從迭代器的?GetCurrent() 方法取出當前元素的值(根據 MoveNext()?移動后的下標)。
?
抽象的 UML 類圖
圖
圖
?
思考
【1】為什么要使用 Iterator 迭代器模式呢?對于集合,或者數組,我們直接使用 for 和 foreach 不就可以了嗎?
圖
觀察上述代碼,我們發現在 while 循環內只涉及方法 MoveNext() 和 GetCurrent(),不依賴集合本身的?Dish 類對象,在遍歷時與集合沒有強耦合的關系,遍歷和實現進行了分離。
也就是說,無論集合 Dish 本身如何變化,只要能夠正常返回 iterator 迭代器,我們就可以正常遍歷。
設計模式的作用就是幫助我們編寫可復用的類。所謂“可復用”,是指將類當成“組件”,當一個組件發生變化時,會盡可能的減少對其他組件的影響,其他組件只需更少的修改或者不需要修改就可以繼續正常工作。
【2】為什么我們有?ConcreteEnumerable 和?ConcreteIterator 兩個具體類,還要額外創建一層接口呢?
我們總是幻想著使用實體類來解決遇到的所有問題。如果只使用具體類來解決問題,很容易增加類之間的強耦合度,這部分類也難以當成組件多次利用。為了降低類之間的耦合度,為了增加類的利用度,從而引入了抽象類和接口。
?
? 【總結】優先使用抽象類和接口來進行編程,而不要總想著采用具體類來實現編程。
?
?
【博主】反骨仔
【原文】http://www.cnblogs.com/liqingwen/p/6550794.html?
?
總結
以上是生活随笔為你收集整理的[C# 设计模式] Iterator - 迭代器模式:我与一份奥利奥早餐的故事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网站制作中常用的一些网页布局
- 下一篇: 企业云存储采用率将在2017年飙升