面向對象23種設計模式系列(四)- 迭代器模式

迭代器模式(Iterator Pattern)html

  一、迭代器模式是設計模式中行爲型模式(behavioral pattern)的一個例子,他是一種簡化對象間通信的模式,也是一種很是容易理解和使用的模式。簡單來講,迭代器模式使得你可以獲取到序列中的全部元素而不用關心是其類型是array,list,linked list或者是其餘什麼序列結構。這一點使得可以很是高效的構建數據處理通道(data pipeline)--即數據可以進入處理通道,進行一系列的變換,或者過濾,而後獲得結果。事實上,這正是Linq的核心模式。數據庫

  二、在.NET中,迭代器模式被IEnumerator和IEnumerable及其對應的泛型接口所封裝。若是一個類實現了IEnumerable接口,那麼就可以被迭代;調用GetEnumerator方法將返回IEnumerator接口的實現,它就是迭代器自己。迭代器相似數據庫中的遊標,他是數據序列中的一個位置記錄。迭代器只能向前移動,同一數據序列中能夠有多個迭代器同時對數據進行操做。設計模式

  三、含有yield的函數說明它是一個生成器,而不是普通的函數。當程序運行到yield這一行時,該函數會返回值,並保存當前域的全部變量狀態;等到該函數下一次被調用時,會從上一次中斷的地方開始執行,一直遇到下一個yield,程序返回值,並在此保存當前狀態; 如此反覆,直到函數正常執行完成。數組

  四、yield是語法糖,編譯時由編譯器生成Iterrator的代碼,包括MoveNext、Current、Reset等。數據結構

1、迭代器模式的實現原理

首先咱們先來看個例子:框架

/// <summary>
/// 食物
/// </summary>
public class Food
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
}

/// <summary>
/// 肯德基菜單
/// </summary>
public class KFCMenu
{
    private Food[] _foodList = new Food[3];
    public KFCMenu()
    {
        this._foodList[0] = new Food()
        {
            Id = 1,
            Name = "漢堡包",
            Price = 15
        };
        this._foodList[1] = new Food()
        {
            Id = 2,
            Name = "可樂",
            Price = 10
        };
        this._foodList[2] = new Food()
        {
            Id = 3,
            Name = "薯條",
            Price = 8
        };
    }

    public Food[] GetFoods()
    {
        return this._foodList;
    }
}

/// <summary>
/// 麥當勞菜單
/// </summary>
public class MacDonaldMenu
{
    private List<Food> _foodList = new List<Food>();
    public MacDonaldMenu()
    {
        this._foodList.Add(new Food()
        {
            Id = 1,
            Name = "雞肉卷",
            Price = 15
        });
        this._foodList.Add(new Food()
        {
            Id = 2,
            Name = "紅豆派",
            Price = 10
        });
        this._foodList.Add(new Food()
        {
            Id = 3,
            Name = "薯條",
            Price = 9
        });
    }

    public List<Food> GetFoods()
    {
        return this._foodList;
    }
}
class Program
{
    static void Main(string[] args)
    {
        {
            KFCMenu kfcMenu = new KFCMenu();
            Food[] foodCollection = kfcMenu.GetFoods();
            for (int i = 0; i < foodCollection.Length; i++)
            {
                Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
            }
        }

        {
            MacDonaldMenu macDonaldMenu = new MacDonaldMenu();
            List<Food> foodCollection = macDonaldMenu.GetFoods();
            for (int i = 0; i < foodCollection.Count(); i++)
            {
                Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
            }
        }

        Console.ReadKey();
    }
}

從上面的例子能夠發現肯德基菜單和麥當勞菜單差很少,可是呢一個是數組存放一個是集合存放,這就致使了它們二者的訪問方式不太同樣。函數

那麼從某種角度上看咱們固然但願它們二者能有一個統一的訪問方式工具

class Program
{
    static void Main(string[] args)
    {
        {
            KFCMenu kfcMenu = new KFCMenu();
            Food[] foodCollection = kfcMenu.GetFoods();
            for (int i = 0; i < foodCollection.Length; i++)
            {
                Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
            }

            foreach (var item in foodCollection)
            {
                Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); }
        }

        {
            MacDonaldMenu macDonaldMenu = new MacDonaldMenu();
            List<Food> foodCollection = macDonaldMenu.GetFoods();
            for (int i = 0; i < foodCollection.Count(); i++)
            {
                Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
            }

            foreach (var item in foodCollection)
            {
                Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); }
        }

        Console.ReadKey();
    }
}

能夠發現使用foreach後它們二者的訪問方式就統一了。那麼這個foreach是怎麼設計出來的呢?其實這就用到了迭代器,迭代器能夠爲不一樣的數據結構提供一個通用的訪問方式測試

下面咱們直接經過代碼來看下迭代器的實現原理:字體

/// <summary>
/// 迭代器抽象類(模擬IEnumerator)
/// </summary>
public interface IIterator<T>
{
    /// <summary>
    /// 當前的對象
    /// </summary>
    T Current { get; }

    /// <summary>
    /// 移動到下一個對象,是否存在。
    /// </summary>
    /// <returns></returns>
    bool MoveNext();

    /// <summary>
    /// 重置
    /// </summary>
    void Reset();
}

/// <summary>
/// 抽象聚合類(模擬IEnumerable)
/// </summary>
public interface IAggregate<T>
{
    IIterator<T> GetEnumerator();
}
/// <summary>
/// 迭代器具體類
/// 肯德基菜單迭代器
/// </summary>
public class KFCMenuIterator : IIterator<Food>
{
    private Food[] _foodList = null;
    public KFCMenuIterator(KFCMenu kfcMenu)
    {
        this._foodList = kfcMenu.GetFoods();
    }

    private int _currentIndex = -1;
    public Food Current
    {
        get
        {
            return this._foodList[_currentIndex];
        }
    }

    public bool MoveNext()
    {
        return this._foodList.Length > ++this._currentIndex; //此處判斷方式是.Length
    }

    public void Reset()
    {
        this._currentIndex = -1;
    }
}

/// <summary>
/// 迭代器具體類
/// 麥當勞菜單迭代器
/// </summary>
public class MacDonaldIterator : IIterator<Food>
{
    private List<Food> _foodList = null;
    public MacDonaldIterator(MacDonaldMenu macDonaldMenu)
    {
        this._foodList = macDonaldMenu.GetFoods();
    }

    private int _currentIndex = -1;
    public Food Current
    {
        get
        {
            return this._foodList[_currentIndex];
        }
    }

    public bool MoveNext()
    {
        return this._foodList.Count > ++this._currentIndex; //此處判斷方式是.Count
    }

    public void Reset()
    {
        this._currentIndex = -1;
    }
}
/// <summary>
/// 肯德基菜單
/// 實現IAggregate
/// </summary>
public class KFCMenu : IAggregate<Food>
{
    private Food[] _foodList = new Food[3];
    public KFCMenu()
    {
        this._foodList[0] = new Food()
        {
            Id = 1,
            Name = "漢堡包",
            Price = 15
        };
        this._foodList[1] = new Food()
        {
            Id = 2,
            Name = "可樂",
            Price = 10
        };
        this._foodList[2] = new Food()
        {
            Id = 3,
            Name = "薯條",
            Price = 8
        };
    }

    public Food[] GetFoods()
    {
        return this._foodList;
    }

    public IIterator<Food> GetEnumerator()
    {
        return new KFCMenuIterator(this);
    }
}

/// <summary>
/// 麥當勞菜單
/// 實現IAggregate
/// </summary>
public class MacDonaldMenu : IAggregate<Food>
{
    private List<Food> _foodList = new List<Food>();
    public MacDonaldMenu()
    {
        this._foodList.Add(new Food()
        {
            Id = 1,
            Name = "雞肉卷",
            Price = 15
        });
        this._foodList.Add(new Food()
        {
            Id = 2,
            Name = "紅豆派",
            Price = 10
        });
        this._foodList.Add(new Food()
        {
            Id = 3,
            Name = "薯條",
            Price = 9
        });
    }

    public List<Food> GetFoods()
    {
        return this._foodList;
    }

    public IIterator<Food> GetEnumerator()
    {
        return new MacDonaldIterator(this);
    }
}

使用以下(紅色字體部分):

using System;
using System.Collections.Generic;
using System.Linq;

using IteratorPattern.Iterator;
using IteratorPattern.Menu;

namespace IteratorPattern
{
    /// <summary>
    /// 迭代器模式(yield return)
    ///     一、迭代器模式是設計模式中行爲型模式(behavioral pattern)的一個例子,他是一種簡化對象間通信的模式,也是一種很是容易理解和使用的模式。
    ///        簡單來講,迭代器模式使得你可以獲取到序列中的全部元素而不用關心是其類型是array,list,linked list或者是其餘什麼序列結構。
    ///        這一點使得可以很是高效的構建數據處理通道(data pipeline)--即數據可以進入處理通道,進行一系列的變換,或者過濾,而後獲得結果。
    ///        事實上,這正是LINQ的核心模式。Linq to object的延遲查詢,按需獲取。
    ///     二、在.NET中,迭代器模式被IEnumerator和IEnumerable及其對應的泛型接口所封裝。若是一個類實現了IEnumerable接口,那麼就可以被迭代;
    ///        調用GetEnumerator方法將返回IEnumerator接口的實現,它就是迭代器自己。迭代器相似數據庫中的遊標,他是數據序列中的一個位置記錄。
    ///        迭代器只能向前移動,同一數據序列中能夠有多個迭代器同時對數據進行操做。
    ///     三、含有yield的函數說明它是一個生成器,而不是普通的函數。當程序運行到yield這一行時,該函數會返回值,並保存當前域的全部變量狀態;
    ///        等到該函數下一次被調用時,會從上一次中斷的地方開始執行,一直遇到下一個yield,程序返回值,並在此保存當前狀態; 如此反覆,直到函數正常執行完成。
    ///     四、yield是語法糖,編譯時由編譯器生成Iterrator的代碼,包括MoveNext、Current、Reset等。
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            {
                KFCMenu kfcMenu = new KFCMenu();
                Food[] foodCollection = kfcMenu.GetFoods();
                for (int i = 0; i < foodCollection.Length; i++)
                {
                    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
                }

                foreach (var item in foodCollection)
                {
                    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);
                }

                IIterator<Food> foodIterator = kfcMenu.GetEnumerator();
                while (foodIterator.MoveNext())
                {
                    Food food = foodIterator.Current;
                    Console.WriteLine("KFC: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price); }
            }

            {
                MacDonaldMenu macDonaldMenu = new MacDonaldMenu();
                List<Food> foodCollection = macDonaldMenu.GetFoods();
                for (int i = 0; i < foodCollection.Count(); i++)
                {
                    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price);
                }

                foreach (var item in foodCollection)
                {
                    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price);
                }

                IIterator<Food> foodIterator = macDonaldMenu.GetEnumerator();
                while (foodIterator.MoveNext())
                {
                    Food food = foodIterator.Current;
                    Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price); }
            }

            Console.ReadKey();
        }
    }
}

能夠發現使用迭代器模式後咱們作到了二者訪問方式的統一。

在C# 1.0中咱們常用foreach來遍歷一個集合中的元素,然而一個類型要可以使用foreach關鍵字來對其進行遍歷必須實現IEnumerable或IEnumerable<T>接口

之因此必需要實現IEnumerable這個接口,是由於foreach是迭代語句,要使用foreach就必需要有一個迭代器才行。

IEnumerable接口中就有IEnumerator GetEnumerator()方法是返回迭代器的,實現了IEnumerable接口就必須實現GetEnumerator()這個方法來返回迭代器,有了迭代器天然就可使用foreach語句了。

在C# 1.0中要實現一個迭代器就必須實現IEnumerator接口中的bool MoveNext()和void Reset()方法。

而在C# 2.0中提供了yield關鍵字來簡化迭代器的實現,這樣在C# 2.0中若是咱們要自定義一個迭代器就容易多了。

2、在C#1.0中實現迭代器

在C# 1.0 中實現一個迭代器必須實現IEnumerator接口,下面代碼演示了傳統方式來實現一個自定義的迭代器:

using System;

namespace IteratorPattern.IteratorImpl
{
    /// <summary>
    /// 朋友類
    /// </summary>
    public class Friend
    {
        private string _name;
        public string Name { get => _name; set => _name = value; }

        public Friend(string name)
        {
            this._name = name;
        }
    }
}
using System.Collections;

namespace IteratorPattern.IteratorImpl.Demo1
{
    /// <summary>
    /// 自定義迭代器,必須實現IEnumerator接口
    /// </summary>
    public class FriendIterator : IEnumerator
    {
        private readonly Friends _friends;
        private int _index;
        private Friend _current;
        internal FriendIterator(Friends friends)
        {
            this._friends = friends;
            _index = 0;
        }

        #region 實現IEnumerator接口中的方法

        public object Current
        {
            get
            {
                return this._current;
            }
        }

        public bool MoveNext()
        {
            if (_index + 1 > _friends.Count)
            {
                return false;
            }
            else
            {
                this._current = _friends[_index];
                _index++;
                return true;
            }
        }

        public void Reset()
        {
            _index = 0;
        }

        #endregion 實現IEnumerator接口中的方法
    }
}
using System.Collections;

namespace IteratorPattern.IteratorImpl.Demo1
{
    /// <summary>
    /// 朋友集合
    /// </summary>
    public class Friends : IEnumerable
    {
        private Friend[] _arrFriend;
        public Friends()
        {
            _arrFriend = new Friend[]
            {
                new Friend("張三"),
                new Friend("李四"),
                new Friend("王五")
            };
        }

        /// <summary>
        /// 索引器
        /// </summary>
        public Friend this[int index]
        {
            get { return _arrFriend[index]; }
        }

        public int Count
        {
            get { return _arrFriend.Length; }
        }

        /// <summary>
        /// 實現IEnumerable接口方法
        /// </summary>
        public IEnumerator GetEnumerator()
        {
            return new FriendIterator(this);
        }
    }
}

使用foreach方式遍歷以下所示:

//在C#1.0中實現迭代器
{
    Console.WriteLine("在C#1.0中實現迭代器");
    var friendCollection = new IteratorImpl.Demo1.Friends();
    foreach (Friend item in friendCollection)
    {
        Console.WriteLine(item.Name);
    }
}

運行結果以下:

3、在C#2.0中實現迭代器

在C# 1.0 中要實現一個迭代器須要實現IEnumerator接口,這樣就必須實現IEnumerator接口中的MoveNext、Reset方法和Current屬性。而在C# 2.0 中經過yield return語句簡化了迭代器的實現。

下面來看看C# 2.0中簡化迭代器的寫法:

using System;

namespace IteratorPattern.IteratorImpl
{
    /// <summary>
    /// 朋友類
    /// </summary>
    public class Friend
    {
        private string _name;
        public string Name { get => _name; set => _name = value; }

        public Friend(string name)
        {
            this._name = name;
        }
    }
}
using System.Collections;

namespace IteratorPattern.IteratorImpl.Demo2
{
    /// <summary>
    /// 朋友集合
    /// </summary>
    public class Friends : IEnumerable
    {
        private Friend[] _arrFriend;
        public Friends()
        {
            _arrFriend = new Friend[]
            {
                new Friend("張三"),
                new Friend("李四"),
                new Friend("王五")
            };
        }

        /// <summary>
        /// 索引器
        /// </summary>
        public Friend this[int index]
        {
            get { return _arrFriend[index]; }
        }

        public int Count
        {
            get { return _arrFriend.Length; }
        }

        /// <summary>
        /// C# 2.0中簡化迭代器的實現
        /// </summary>
        public IEnumerator GetEnumerator()
        {
            for (int index = 0; index < _arrFriend.Length; index++)
            {
                // 這樣就不須要額外定義一個FriendIterator迭代器來實現IEnumerator
                // 在C# 2.0中只須要使用下面語句就能夠實現一個迭代器
                yield return _arrFriend[index];
            }
        }
    }
}

使用foreach方式遍歷以下所示:

//在C#2.0中實現迭代器
{
    Console.WriteLine("在C#2.0中實現迭代器");
    var friendCollection = new IteratorImpl.Demo2.Friends();
    foreach (Friend item in friendCollection)
    {
        Console.WriteLine(item.Name);
    }
}

運行結果以下:

在上面代碼中有一個yield return語句,這個語句的做用就是告訴編譯器GetEnumerator方法不是一個普通的方法,而是一個實現迭代器的方法。

當編譯器看到yield return語句時,編譯器就知道須要實現一個迭代器,因此編譯器生成中間代碼時爲咱們生成了一個IEnumerator接口的對象,你們能夠經過反編譯工具進行查看。

yield return語句實際上是C#中提供的一個語法糖,簡化咱們實現迭代器的代碼,把具體實現複雜迭代器的過程交給編譯器幫咱們去完成。

4、迭代器的執行過程

爲了讓你們更好的理解迭代器,下面列出迭代器的執行流程:

5、迭代器的延遲計算

從第四部分迭代器的執行過程當中能夠知道迭代器是延遲計算的,由於迭代的主體在MoveNext()中實現(在MoveNext()方法中訪問了集合中的當前位置的元素)。

foreach中每次遍歷執行到in的時候纔會調用MoveNext()方法,因此迭代器能夠延遲計算,下面經過一個示例來演示迭代器的延遲計算:

using System;
using System.Collections.Generic;

namespace IteratorPattern
{
    /// <summary>
    /// yield是語法糖,編譯時由編譯器生成Iterrator的代碼
    /// </summary>
    public class YieldDemo
    {
        /// <summary>
        /// 含有迭代器的
        /// </summary>
        public static IEnumerable<int> WithIterator()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"在WithIterator方法中的,當前i的值爲:{i}");
                if (i > 1)
                {
                    yield return i;
                }
            }
        }

        /// <summary>
        /// 不包含迭代器的
        /// </summary>
        public static IEnumerable<int> WithoutIterator()
        {
            List<int> list = new List<int>();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"在WithoutIterator方法中的,當前i的值爲:{i}");
                if (i > 1)
                {
                    list.Add(i);
                }
            }

            return list;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        //迭代器的延遲計算
        {
            // 測試一
            YieldDemo.WithIterator();

            // 測試二
            YieldDemo.WithoutIterator();

            // 測試三
            foreach (var item in YieldDemo.WithIterator()) //按需獲取,要一個拿一個
            {
                Console.WriteLine($"在Main函數的輸出語句中,當前i的值爲:{item}");
                if (item >= 3)
                {
                    break;
                }
            }

            // 測試四
            foreach (var item in YieldDemo.WithoutIterator()) //先所有獲取,而後一塊兒返回
            {
                Console.WriteLine($"在Main函數的輸出語句中,當前i的值爲:{item}");
                if (item >= 3)
                {
                    break;
                }
            }
        }

        Console.ReadKey();
    }
}

運行測試一結果以下:

運行測試一的代碼時會發現控制檯中什麼都不輸出,這是爲何呢?下面咱們經過反編譯工具來看下緣由:

PS:此Demo的目標框架最好是Framework版本的,只有這樣才方便經過反編譯工具查看原理。

從反編譯的結果中咱們就能夠看出測試一什麼都不輸出的緣由了,那是由於WithIterator方法中含有yield關鍵字,編譯器遇到yield return語句就會幫咱們生成一個迭代器類。

從而當咱們在測試一的代碼中調用YieldDemo.WithIterator()時,對於編譯器而言其實就是實例化了一個YieldDemo.<WithIterator>d__0的對象而已,因此運行測試一的代碼時控制檯中什麼都不輸出。

運行測試二結果以下:

運行測試二結果就如咱們指望的那樣輸出,這裏就很少解釋了。

運行測試三結果以下:

運行測試四結果以下:

對比測試三和測試四的結果能夠發現迭代器是能夠作到延遲計算、按需獲取的。

6、關於迭代器模式的一些小擴展

using System;
using System.Collections.Generic;

namespace IteratorPattern.Show
{
    public static class ExtendMethod
    {
        public static IEnumerable<T> TianYaWhere<T>(this IEnumerable<T> source, Func<T, bool> func)
        {
            if (source == null)
            {
                throw new Exception("source is null");
            }

            if (func == null)
            {
                throw new Exception("func is null");
            }

            foreach (var item in source)
            {
                if (func.Invoke(item))
                {
                    yield return item;
                }
            }
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;

namespace IteratorPattern.Show
{
    public static class LinqExtend
    {
        public static IEnumerable<TSource> TianYaWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            if (source == null)
            {
                throw new Exception("source");
            }

            if (predicate == null)
            {
                throw new Exception("predicate");
            }

            return new EnumeratorIterator<TSource>(source, predicate);
        }
    }

    public class EnumeratorIterator<TSource> : IEnumerable<TSource>
    {
        private IEnumerable<TSource> _source;
        private Func<TSource, bool> _predicate;
        public EnumeratorIterator(IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            this._source = source;
            this._predicate = predicate;
        }

        public IEnumerator<TSource> GetEnumerator()
        {
            foreach (var item in this._source)
            {
                if (_predicate(item))
                {
                    yield return item;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var item in this._source)
            {
                if (_predicate(item))
                {
                    yield return item;
                }
            }
        }
    }
}

至此本文就所有介紹完了,若是以爲對您有所啓發請記得點個贊哦!!!

本文部份內容參考博文:https://www.cnblogs.com/zhili/archive/2012/12/02/Interator.html

 

Demo源碼:

連接:https://pan.baidu.com/s/1FqAvYAZhrKuCLzuJcTZ5KA 
提取碼:fx1g

此文由博主精心撰寫轉載請保留此原文連接:https://www.cnblogs.com/xyh9039/p/13894175.html

相關文章
相關標籤/搜索