设计模式之迭代器模式

StackOverflow
• 阅读 732

本文介绍设计模式中的迭代器模式,首先通俗的解释迭代器模式的基本概念和对应的四个角色,并根据四个角色举一个典型的实例,为了加强知识的连贯性,我们以Jdk源码集合中使用迭代器模式的应用进一步说明,最后说明迭代器模式的应用场景和优缺点。

读者可以拉取完整代码本地学习,实现代码均测试通过上传到码云

一、概念理解

迭代器模式官方解释就是提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。何为聚合对象呢?最典型的就是集合类。

大白话也就是,集合中的数据是私有的,集合中不应该提供直接遍历的方法,要定义一个新的对象用于访问这个集合。

既然是一个专门用来遍历的对象,一个被遍历的聚合对象,很显然至少有两个对象,迭代器对象、聚合对象,由于遵循面向接口编程的原则,迭代器对象和聚合对象应该抽象出来接口,那自然而然就是应该有四个角色:

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

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

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

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

基于四个角色我们举一个典型案例。

二、案例实现

应该是有四个类

抽象聚合角色,用于定义增删改查元素的统一规范接口,和创建迭代器对象的方法

具体聚合角色,实现抽象聚合角色方法

抽象迭代器角色,定义遍历元素的统一规范接口

具体迭代器,实现抽象迭代器角色的方法。

抽象聚合角色:

/**
 * 抽象聚合角色
 * @author tcy
 * @Date 13-09-2022
 */
public interface InterfaceAggregate {
    /**
     * 增加对象
     * @param obj 对象
     */
    void add(Object obj);

    /**
     * 移除对象
     * @param obj 对象
     */
    void remove(Object obj);

    /**
     * 调用迭代器
     * @return 迭代器
     */
    Iterator getIterator();
}

具体聚合角色:

/**
 * 具体聚合角色
 * @author tcy
 * @Date 13-09-2022
 */
public class ConcreteAggregate implements InterfaceAggregate{
    private List<Object> list = new ArrayList<>();
    @Override
    public void add(Object obj) {
        list.add(obj);
    }

    @Override
    public void remove(Object obj) {
        list.remove(obj);
    }

    @Override
    public Iterator getIterator() {
        return new Concretelterator(list);
    }
}

抽象迭代器角色:

/**
 * 抽象迭代器
 * @author tcy
 * @Date 13-09-2022
 */
public interface Iterator<E> {

    /**
     * 删除对象
     * @return 对象
     */
    Object remove();

    /**
     * 调用下一个对象
     * @return 对象
     */
    E next();

    /**
     * 迭代器中是否还有下一个对象
     * @return
     */
    boolean hasNext();

    /**
     * 遍历迭代器中剩余的对象
     * @param action
     */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

}

具体迭代器角色:

/**
 * 具体迭代器角色
 * @author tcy
 * @Date 13-09-2022
 */
public class Concretelterator implements Iterator{
    private List<Object> list = null;
    private int index = -1;

    public Concretelterator(List<Object> list) {
        this.list = list;
    }

    @Override
    public Object remove() {
        index = list.size();
        Object obj = list.get(index);
        list.remove(obj);
        return obj;
    }

    @Override
    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = list.get(++index);
        }
        return obj;
    }

    @Override
    public boolean hasNext() {
        if (index < list.size() - 1) {
            return true;
        } else {
            return false;
        }
    }
}

客户端调用:

/**
 * @author tcy
 * @Date 13-09-2022
 */
public class Client {

    public static void main(String[] args) {
        ConcreteAggregate concreteAggregate=new ConcreteAggregate();
        concreteAggregate.add("老王");
        concreteAggregate.add("小王");
        concreteAggregate.add("小张");

        System.out.println("Aggregate聚合对象有:");

        Iterator iterator=concreteAggregate.getIterator();

        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next.toString());
        }
        //遍历剩下的角色
        iterator.forEachRemaining(ele -> System.out.println(ele));

    }

}

迭代器实现逻辑比较清晰,理解起来难度也不大,了解了该设计模式,趁热打铁看迭代器模式在源码中的应用。

三、源码应用

迭代器模式在Jdk中的集合类中有着广泛的应用,我们以ArrayList作为典型。

在ArrayList实现迭代器时,同样是有四个角色。

List抽象聚合类;

ArrayList具体聚合角色;

Iterator抽象迭代器;

ArrayList内部类Itr是具体迭代器;

我们可以看到ArrayList是把具体聚合角色和具体迭代器都写在一个类中,Itr作为具体迭代对象是以内部类的形式。

ArrayList其实和我们案例中的方法长的很像,只不过ArrayList中定义了更多的方法,而且ArrayList还有一个内部类ListItr。

设计模式之迭代器模式

其实是迭代器的增强版,在继承Itr的基础之上实现ListIterator接口。

Iterator迭代器除了,hasNext()、next()、remove()方法以外,还有一个特别的forEachRemaining()方法,我们重点说下forEachRemaining()方法,该方法代表的意思是遍历剩下的集合。

比如我们已经调用了该集合中的第一个元素,那么遍历时候就会自动忽略第一个元素,遍历剩下的元素。

我们写一个测试方法看效果:

public class Client {

    public static void main(String[] args) {
      
        // jdk ArrayList迭代器
        //创建一个元素类型为Integer的集合
        Collection<String> collection =  new ArrayList<>();

            //向集合中添加元素
            collection.add("老王");
            collection.add("小王");
            collection.add("小张");

        //获取该集合的迭代器
        java.util.Iterator<String> iteratorJdk= collection.iterator();
        System.out.println("Arraylist聚合对象有:");
        //调用迭代器的经过集合实现的抽象方法遍历集合元素
        while(iteratorJdk.hasNext())
        {
            System.out.println(iteratorJdk.next());
        }
        //调用forEachRemaining()方法遍历集合元素
        iteratorJdk.forEachRemaining(ele -> System.out.println(ele));

    }

}
Arraylist聚合对象有:
老王
小王
小张

正常情况下,会打印两次集合对象中的信息,实际上只打印了一次,正是由于next调用过的元素,forEachRemaining不会再调。

看到这,想必你对迭代器已经有了初步的了解,当在遍历元素时,除了使用for循环遍历元素以外,提供了另外一种方式遍历元素。

案例很好理解,源码中的应用也看得懂,但是实际开发中迭代器对象什么时候用呢?想必大部分人并不是很清晰。

接着看迭代器对象的应用场景和优缺点,看从中能不能找到答案。

四、总结

当一个对象是一个聚合对象且需要对外提供遍历方法时,可以使用迭代器模式,也即实际业务中定义的有聚合对象,里面存放了我们需要的业务数据,为了让业务数据的职责更清晰,我们就可以将编辑的方法提取出来,另外定义一个迭代器对象用于遍历数据。

迭代器方式提供了不同的方式遍历聚合对象,增加新的聚合类和迭代器类都是比较方便的,Java集合类中庞大的家族采用迭代器模式就是基于这种优点。

迭代器模式有设计模式的通用缺点——系统复杂性,迭代器模式将数据存储和数据遍历分开,增加了类的个数。

我已经连续更新了十几篇设计模式博客,推荐你一起学习。

一、设计模式概述

二、设计模式之工厂方法和抽象工厂

三、设计模式之单例和原型

四、设计模式之建造者模式

五、设计模式之代理模式

六、设计模式之适配器模式

七、设计模式之桥接模式

八、设计模式之组合模式

九、设计模式之装饰器模式

十、设计模式之外观模式

十一、外观模式之享元模式

十二、设计模式之责任链模式

十三、设计模式之命令模式

十四、设计模式之解释器模式

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
徐小夕 徐小夕
5年前
《前端实战总结》之迭代器模式的N+1种应用场景
眼看12月就来了,抓住今年的尾巴,好好总结一下前端的不足与收获。这篇文章是笔者写设计模式专题的第二篇文章,也是基于工作中的总结和提炼,在实际应用场景中都会大量使用,至于为什么要写设计模式,主要是为了提高团队代码质量和可维护性,后续会继续推出设计模式相关的文章,供大家参考和学习。你将学到迭代器模式的含义实现一个数组迭代器实现一个对象迭代器
徐小夕 徐小夕
5年前
《前端实战总结》之使用解释器模式实现获取元素Xpath路径的算法
前端领域里基于javascript的设计模式和算法有很多,在很多复杂应用中也扮演着很重要的角色,接下来就介绍一下javascript设计模式中的解释器模式,并用它来实现一个获取元素Xpath路径的算法。上期回顾《前端实战总结》之迭代器模式的N1种应用场景(https://juejin.im/post/6844904008616771591)
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
Java描述设计模式(13):迭代器模式
本文源码:GitHub·点这里(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fcicadasmile%2Fmodelarithmeticparent)||GitEE·点这里(https://gitee.com/cicadasmile/modela
Stella981 Stella981
4年前
OpenCV访问像素点
三种方法迭代器创建一个Mat::Iterator对象it,通过itMat::begin()来的到迭代首地址,递增迭代器知道itMat::end()结束迭代;while(it!Scr.end<Vec3b()){//(it)00;//蓝色通道置零;
Wesley13 Wesley13
4年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的