面向对象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

相关文章
相关标签/搜索