zoukankan      html  css  js  c++  java
  • [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 

  • 相关阅读:
    迭代器
    装饰器
    函数对象和闭包
    函数的使用
    文件操作
    基本数据类型及内置方法
    MySQL数据库
    网络编程进阶(进程、线程、协程、IO模型)
    网络编程基础---网络通讯原理、ssh远程执行命令、粘包问题处理、文件传输处理
    面向对象、类、元类、封装、异常处理
  • 原文地址:https://www.cnblogs.com/liqingwen/p/6550794.html
Copyright © 2011-2022 走看看