前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >迭代器模式

迭代器模式

作者头像
小蜜蜂
修改2019-07-16 17:25:13
6220
修改2019-07-16 17:25:13
举报
文章被收录于专栏:明丰随笔明丰随笔

迭代器模式的定义

提供一种统一的方法遍历一个集合中的各个元素,而不关心集合的内部实现。

迭代器模式的目的

在面向对象编程里,迭代器模式是一种最简单也最常见的设计模式。它可以让用户透过特定的接口访问集合中的每一个元素而不用了解底层的实现。一般实现一个集合的方法有:数组,链表,哈希表等等,每种集合因为底层实现不同,遍历集合的方法也不同。对于数组或者列表,用户需要在对集合了解很清楚的前提下,可以自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦,而且暴露了集合类的内部表示给用户,数据也会不安全。而引入了迭代器方法后,用户用起来就简单的多了,并且更加安全。迭代器模式在客户访问类与集合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”。所以,如果我们对各种集合的都实现了迭代器接口,就可以使存储数据和遍历数据的职责分离,并且让外部代码可以透明并统一地访问集合内部的数据,简化了遍历方式,还提供了良好的封装性,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用关心。

迭代器模式的优点

1.访问一个聚合对象的内容而无须暴露它的内部表示。

2.遍历任务交由迭代器完成,这简化了集合类。

3.它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。

4.增加新的集合类和迭代器类都很方便,无须修改原有代码。

5.封装性良好,为遍历不同的聚合结构提供一个统一的接口。

迭代器模式的缺点

增加了类的个数,这在一定程度上增加了系统的复杂性。

迭代器模式的应用场景

1.需要为聚合对象提供多种遍历方式。

2.需要为遍历不同的聚合结构提供一个统一的接口。

3.访问一个聚合对象的内容而无须暴露其内部细节的表示。

迭代器模式的结构

迭代器模式把存储数据和遍历数据的职责分离,所以它需要2个类:集合类和迭代器类。因为需要接口编程,所以,在迭代器模式中,抽象了2个接口,一个是集合接口,另一个是迭代器接口,具体的角色如下:

1.抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。

2.具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。

3.抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。

4.具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

具体的结构类图如下所示:

迭代器模式的经典实现

代码语言:javascript
复制
public interface Aggregate
{
    void add(object obj);
    void remove(object obj);
    Iterator getIterator();
}

public interface Iterator
{
    object first();
    object next();
    bool hasNext();
}

public class ConcreteAggregate : Aggregate
{
    private List<object> _list = new List<object>()
    {
        "Nestor","Liu"
    };

    public void add(object obj)
    {
        _list.Add(obj);
    }

    public Iterator getIterator()
    {
        return new ConcreteIterator(_list);
    }

    public void remove(object obj)
    {
        _list.Remove(obj);
    }
}

public class ConcreteIterator : Iterator
{
    private List<object> _list = null;
    private int _index = -1;

    public ConcreteIterator(List<object> list)
    {
        _list = list;
    }

    public object first()
    {
        _index = 0;
        object obj = _list[_index];
        return obj;
    }

    public bool hasNext()
    {
        return _list.Count() > _index + 1;
    }

    public object next()
    {
        return hasNext() ? _list[++_index] : null;
    }
}

ConcreteAggregate aggregate = new ConcreteAggregate();
Iterator iterator = aggregate.getIterator();

while (iterator.hasNext())
{
    Console.WriteLine(iterator.next().ToString());
}

.NET中迭代器模式的规范

在.NET下,迭代器模式中的聚集接口和迭代器接口都已经存在了,其中IEnumerator接口扮演的就是迭代器角色,IEnumberable接口则扮演的就是抽象聚集的角色,只有一个GetEnumerator()方法,关于这两个接口的定义可以自行参考MSDN。根据定义,Microsoft .NET Framework集合是至少实现IEnumerable<T>(或非泛型IEnumerable接口)的类。此接口至关重要,因为至少必须实现IEnumerable<T>的方法,才支持迭代集合。IEnumerable和IEnumerator接口的类图:

这样实现的好处在集合类不直接支持IEnumerator接口,而是直接支持另一种接口IEnumerable,其唯一方法是GetEnumerator。此方法用于返回支持 IEnumerator的对象。IEnumerator的对象就像是序列中的“游标”或“书签”。一个集合可以有多个“书签”,移动其中任何一个都可以枚举集合,与其他枚举器互不影响。

.NET规范实现代器模式:

代码语言:javascript
复制
public class MyEnumerable : IEnumerable
{
    private List<object> _list = new List<object>()
    {
        "Nestor", "Liu"
    };

    public IEnumerator GetEnumerator()
    {
        return new MyEnumerator(_list);
    }
}

public class MyEnumerator : IEnumerator
{
    private int _index = -1;
    private List<object> _list;

    public MyEnumerator(List<object> list)
    {
        _list = list;
    }

    public object Current
    {
        get
        {
            if (_index == -1 || _index >= _list.Count())
            {
                throw new IndexOutOfRangeException();
            }
            return _list[_index];
        }
    }

    public bool MoveNext()
    {
        return _list.Count() > ++_index;
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }
}

yield语法糖

通过使用yield定义迭代器,可在实现自定义集合类型迭代器模式时无需其他显式类,使用yield return语句可一次返回一个元素。迭代器方法运行到yield return语句时,会返回一个expression,并保留当前在代码中的位置。下次调用迭代器函数时,将从该位置重新开始执行。可以使用 yield break 语句来终止迭代。C#代码示例:

代码语言:javascript
复制
public class MyEnumerable2 : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return "Nestor";
        yield return "Liu";
    }
}

对于上面yield语法写法虽然没有显式的定义IEnumerator的实现,但是背后的原理是一样的,C#编辑器编译成的IL代码会包含IEnumerator的实现。

.NET Framework中迭代器模式的应用

C#的foreach语句其实就是迭代器模式。任何可以使用foreach进行遍历的对象,它一定是实现了IEnumerable接口。任何实现了IEnumerable接口的对象集合都可以使用foreach遍历。

foreach语句的写法:

代码语言:javascript
复制
public void ForeachTest()
{
    int[] values = new int[] { 1, 2, 3, 4, 5 };

    foreach (int i in values)
    {
        Console.Write(i.ToString() + " ");
    }
    Console.WriteLine();
}

其实等价于下面的写法:

代码语言:javascript
复制
public void IteratorTest()
{
    int[] values = new int[] { 1, 2, 3, 4, 5 };
    IEnumerator e = ((IEnumerable)values).GetEnumerator();
    while (e.MoveNext())
    {
        Console.Write(e.Current.ToString() + " ");
    }
    Console.WriteLine();
}

对于上面两种C#写法,C#编辑器编译成的IL代码是一致的。

迭代器执行过程

以下代码从迭代器方法返回IEnumerable<string>,然后遍历其元素。

代码语言:javascript
复制
IEnumerable<string> elements = MyIteratorMethod();
foreach (string element in elements)
{
   ...
}

调用MyIteratorMethod并不执行该方法的主体。 相反,会将调用的控制权返回到elements变量中。在foreach循环迭代时,将调用elements的MoveNext方法。 此调用将执行MyIteratorMethod的主体,直至到达下一个yield return语句。yield return 语句返回的表达式不仅决定了循环体使用的element变量值,还决定了elements的Current属性。我们通过一张图来看迭代器执行过程:

迭代器模式的使用感受

迭代器模式是与集合类紧密绑定在一起的,一般来说,我们只要实现一个集合类,就应该同时提供这个集合的迭代器,就像C#中的Collection,List、Set、Map等,这些集合都有自己的迭代器。假如我们要实现一个这样的新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器,这样客户在遍历我们的集合时,可以享受到一致的体验。

谢谢观看!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档