1. 程式人生 > >設計模式的征途—21.迭代器(Iterator)模式

設計模式的征途—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()方法。)

參考資料

  DesignPattern

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

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

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

設計模式征途21.Iterator模式

我們都用過電視機遙控器,通過它我們可以進行開機、關機、換臺、改變音量等操作。我們可以將電視機看做一個儲存電視訊道的集合物件,通過遙控器可以對電視機中的頻道集合進行操作,例如返回上一個頻道、跳轉到下一個頻道或者跳轉到指定的頻道等。遙控器的出現,使得使用者不需要知道這些頻道到底如何儲存在電視機中。在軟體開發中也存

Go語言設計模式實踐:Iterator

關於本系列 決定開個新坑。 這個系列首先是關於Go語言實踐的。在專案中實際使用Go語言也有段時間了,一個體會就是不論是官方文件、圖書還是網路資料,關於Go語言慣用法(idiom)的介紹都比較少,基本只能靠看標準庫原始碼自己琢磨,所以我特別想在這方面有一些收集和總結。 然後這個系列也是關於設計模式的。雖然G

c++iterator詳解【轉】

(轉自:https://www.cnblogs.com/hdk1993/p/4419779.html) 1. 迭代器(iterator)是一中檢查容器內元素並遍歷元素的資料型別。 (1) 每種容器型別都定義了自己的迭代器型別,如vector: vector<int>::it

泛型程式設計學習,編寫一個類似STL庫中的簡易list的iterator

泛型程式設計學習,編寫一個類似STL庫中的簡易list的迭代器(iterator) 前言 近期在研究stl原始碼及stl裡各種實現的細節,初學入門免不了模仿,以下便寫一次自己的簡單的list容器的迭代器。 首先,在開始編寫List的迭代器的時候我們首先應該瞭解我們要寫的List和其迭

Scala Iterator

1、簡述   在Scala中,迭代器(Iterator)不是一個集合,是構建了一種訪問集合的方法。當構建一個集合需要很大的開銷時(比如把檔案得所有行都讀取到記憶體),迭代器就發揮了很好的作用。   迭代器有兩個操作,next 和hasNext。next返回迭代器的下一個元素,hasNext用於檢查是否還有

Python的iterator和生成器generator

前言: 迭代的意思是重複做一些事很多次-就像迴圈中那樣,for迴圈中對序列和字典進行迭代,但是實際上也能對其他的物件進行迭代:實現__iter__方法的物件。 迭代器 __iter__方法返回一個迭代器。所謂的迭代器就是具有next方法(方法不需要引數)的物件。在呼叫next方法時,

Python中的容器containeriterator和生成器generator:yield

1、容器迭代器iterator  以list容器(tuple,range,file類似)為例,list容器可以呼叫自己的__iter__()方法獲取用來迭代自己內部元素的迭代器。例如: # list容器 myList = [1, 2, 3] # 獲取list容器的迭代器 it

C# 入門14 列舉enumeratoriterator

C#的列舉器和迭代器      列舉器一般用來foreach的,而迭代器在Unity中常用來當協程(Coroutine)用。 列舉器 IEnumerator介面   實現IEnumerator介面的類就可以foreach了,當然前提是實

修飾符@,裝飾decoratoriterator,生成器Generator的一些見解

       這三個方法,在Python裡的使用並不多,然而,各大網際網路公司還特別喜歡考這一方面的。以前因為不常用,有些忽視了這幾個方法,碰了幾次壁後,決心好好整理一番這三個方法(以下程式碼均在python3.6的環境下實驗):    

C++之STLiterator

1、vector #include <iostream> #include <vector> int main() { std::vector<char> charVector; int x; for (x=0; x&l

STL iterator詳解

背景:指標可以用來遍歷儲存空間連續的資料結構,但是對於儲存空間非連續的,就需要尋找一個行為類似指標的類,來對非陣列的資料結構進行遍歷。因此,我們引入迭代器概念。   一、迭代器(iterator)介紹 迭代器(Iterator)是一種檢查容器內元素並遍歷元素的資料型別。迭代器是指標的泛化,它允

C++iterator的簡單使用

迭代器提供對一個容器中的物件的訪問方法,並且定義了容器中物件的範圍。迭代器就如同一個指標。事實上,C++的指標也是一種迭代器。但是,迭代器不僅僅是指標,因此你不能認為他們一定具有地址值。例如,一個數組索引,也可以認為是一種迭代器。 迭代器有各種不同的建立方法。程式可能把迭代

Strutsiterator遍歷List常用的4種例子

// TODO 給list賦值 if (list==null) { list=new ArrayList<String>(); list.add("劉備"); list.add("關

list+C++簡單操作

#include<iostream> #include<list> using namespace std; int main(){     list<int> list1;     list1.push

SPL的常用3

1.迭代器的介紹 ·什麼是迭代器?  -怎樣獲得連結串列中的每一個節點資訊   節點1<---->節點2<---->節點3   ↑連結串列指標 通過某種統一的方式遍歷連結串列或者陣列中的元素的過程 叫做迭代遍歷,而這種統一的遍歷工具我們叫做迭代器

Python:封裝、多型、多繼承、函式重寫、布林測試函式重寫、高階

用於類的函式: issubclass(cls,class_or_tuple)判斷一個類是否繼承自某個類class或某類tuple中的 一個類,如果是則返回true,否則返回false 示例: class A: pass cla

python小結 函式小白總結&生成器&定義

【def】   定義一個函式    f()  呼叫這個函式        f 只是相當於呼叫一個函式物件,返回的是一個函式的記憶體地址,要搞清楚這一點,這樣會對以後高階函式的理解有幫助 def f(): print "ok" f() 執行結果: ok 【引數】 給函式新增引數:1。 普通的

列舉

C#中的列舉器(enumerator)通俗的講是一個可以遞進式的訪問一個序列的指標(可能這並不恰當)。foreach語句(迭代語句)是我們比較熟悉的,例如順序輸入一個字串中的每個字元: foreach (char c in "foreach") { Console.Writ

《STL原始碼剖析》——iterators概念與traits程式設計技法

一、迭代器設計思維——STL關鍵所在         STL的中心思想在於:將資料容器(containers)和演算法(algorithms)分開,彼此獨立設計,最後再以一帖粘合劑將它們撮合在一起。 二、迭代器(iterator)是一種 smart pointer    

Java(iterator詳解以及和for迴圈的區別)

迭代器是一種模式,它可以使得對於序列型別的資料結構的遍歷行為與被遍歷的物件分離,即我們無需關心該序列的底層結構是什麼樣子的。只要拿到這個物件,使用迭代器就可以遍歷這個物件的內部.1.IteratorJava提供一個專門的迭代器«interface»Iterator,我們可以對某個序列實現該interface,