设计模式学习笔记 - 设计模式与范式 -行为型:9.迭代器模式(上):相比直接遍历集合数据,使用迭代器模式有哪些优势?

概述

上篇文章,我们学习了状态模式。状态模式是状态机的一种实现方式。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。

本章,学习另外一种行为型设计模式,迭代器模式。它用来遍历集合对象。不过,很多编程语言都将迭代器作为一个基础的类库,直接提供出来了。在平时的开发中,特别是业务开发,直接使用即可,很少会自己去实现一个迭代器。不过,知其然知其所以然,弄懂原理能帮助我们更好的使用这些工具类,所以,还是有必要学习一下这个模式。

我们知道,大部分编程语言都提供了多种遍历集合的方式,比如 for 循环、foreach 循环、迭代器等。所以,本章除了讲解迭代器的原理和实现之外,还会重点说一下,相对于其他的遍历方式,利用迭代器来遍历集合的优势。


迭代器模式的实现原理

迭代器模式(Iterator Design Pattern),也叫作游标模式(Cusor Design Pattern)。

它用来遍历集合对象。这里说的 “集合对象” 也叫做 “容器” “聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及到容器容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器包含容器接口、容器实现类,迭代器包含迭代器接口、迭代器实现类。对于迭代器模式,我绘制了一张简单的类图。
在这里插入图片描述
接下来通过一个例子,来讲解如何实现一个迭代器。

概述中提到过,大部分编程语言都提供了遍历容器的迭代器类,在平时开发中,直接拿来使用即可,几乎不大可能从零去编写一个迭代器。不过,这里为了讲解迭代器的实现原理,我们假设某个新的编程语言的基础类库中,还没有提供线性容器对应地迭代器,需要从零开始开发。

线性数据结构包括链表和数组,在大部分编程语言中都有对应地类来封装这两种数据结构,在开发中直接拿来使用就可以了。假设在新的编程语言中,这两个数据结分别对应 ArrayListLinkedList 两个类。此外,我们从两个类中抽象出公共的接口,定义为 List 接口,以方便开发者基于接口而非实现编程。

现在,针对 ArrayListLinkedList 两个线性容器,设计实现对应的迭代器。按照之前给出的迭代器类图,先定义一个接口 Iterator 以及针对这两种容器的迭代器实现类 ArrayIteratorLinkedIterator

先看下 Iterator 接口的定义。具体代码如下所示:

// 接口定义方式一
public interface Iterator<E> {
    boolean hasNext();
    void next();
    E currentItem();
}

// 接口定义方式二
public interface Iterator<E> {
    boolean hasNext();
    E next();
}

Iterator 接口有两种定义方式。

  • 在第一种定义中, next() 函数用来将游标后移一位元素,currentItem() 函数用来返回当前游标执行的元素。
  • 在第二种定义中,返回当前元素与后移一位这两个操作,都要放到同一个函数 next() 中完成。

第一种实现方式更加灵活些,比如可以多次调用 currentItem() 查询当前元素,而不移动游标。所以,在接下来的实现中,我们选择第一种接口定义方式。

现在,我们再来看一下 ArrayIterator 的代码实现,具体如下所示。代码实现非常简单,不需要太多解释。

public class ArrayIterator<E> implements Iterator<E> {
    private int cursor;
    private ArrayList<E> arrayList;

    public ArrayIterator(ArrayList<E> arrayList) {
        this.cursor = 0;
        this.arrayList = arrayList;
    }

    @Override
    public boolean hasNext() {
        return cursor != arrayList.size(); // 注意这里,cursor在指向最后一个元素的时候,hasNext()仍返回true
    }

    @Override
    public void next() {
        cursor++;
    }

    @Override
    public E currentItem() {
        if (cursor >= arrayList.size()) {
            throw new NoSuchElementException();
        }
        return arrayList.get(cursor);
    }
}

public class Demo {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("chen");
        names.add("jian");
        names.add("seng");

        Iterator<String> iterator = new ArrayIterator<>(names);
        while (iterator.hasNext()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}

在上面的视线中,需要将待遍历的容器对象,通过构造函数传递给迭代器类。实际上,为了封装迭代器的创建细节,我们可以在容器中定义一个迭代器的 iterator() 方法,来创建对应的迭代器。为了能基于接口而非实现编程,还需要将这个方法定义在 List 接口中。具体的代码实现和使用如下所示。

public interface List<E> {
    Iterator iterator();
    // 省略其他接口函数...
}

public class ArrayList<E> implements List<E> {
    // ...
    @Override
    public Iterator iterator() {
        return new ArrayIterator(this);
    }
    // 省略其他代码...
}

public class Demo {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("chen");
        names.add("jian");
        names.add("seng");

        Iterator<String> iterator = names.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
}

对于 LinkedIterator,它结构和 ArrayIterator 完全相同,这里就不给出代码了。

结合刚刚的例子,来总结一下迭代器设计思路。

  • 迭代器需要定义 hasNext()currentItem()next() 三个最基本的方法。
  • 待遍历的容器对象通过依赖注入传递到迭代器类中。
  • 容器通过 iterator() 方法来创建迭代器。

下面画了一张类图,你可以结合着看一看。

在这里插入图片描述

迭代器模式的优势

迭代器的原理和实现讲完了,现在,一起来看一下,使用迭代器遍历集合的优势。

一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。对照这三种方式,举例说明下:

List<String> names = new ArrayList<>();
names.add("chen");
names.add("jian");
names.add("seng");

// 第一种遍历方式,for循环
for (int i = 0; i < names.size(); i++) {
    System.out.print(names.get(i) + ",");
}

// 第二种遍历方式,foreach循环
for (String name : names) {
    System.out.print(name + ",");
}

// 第三种遍历方式,迭代器遍历
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next() + ","); // Java 中的迭代器接口是第二种定义方式,next()即移动游标又返回数据
}

实际上 foreach 循环只是一个语法糖而已,底层是基于迭代器来实现的。也就是说,上面的代码中的第二种遍历方式(foreach 循环)的底层实现,就是第三种遍历方式(迭代器遍历)。

从上面的代码来看,for 循环遍历方式比起迭代器遍历方式,代码看起来更加简洁。那为什么还要用迭代器来遍历容器呢?为什么还要给容器设计对应的迭代器呢?原因有三个。

  • 首先,对于数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图),有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。

    前面讲过,应对复杂性的方法就是拆分。可以将遍历操作拆分到迭代器类中。比如,针对图的遍历,可以定义 DFSIteratorBFSIterator 两个迭代器类,让它们分别来实现深度优先和广度优先遍历。

  • 其次,将游标指向的当前位置等信息,存储在迭代器类中,每个迭代器独享游标信息。这样,我们就可以创建多个不同的迭代器类,同时对同一个容器进行遍历而互不影响。

  • 最后,容器和迭代器提供了抽象接口,方便在开发时,基于接口而非实现编程。当需要切换新的遍历算法时,比如,从前往后遍历链表切换成从后往前遍历链表,客户端只要将迭代器类从 LinkedIterator 切换为 ReversedLinkedIterator 即可,其他代码不需要修改。此外添加新的遍历算法,只需要扩展新的迭代器类,也更符合开闭原则。

总结

迭代器模式,也叫游标模式。它用来遍历集合对象。

这里说的 “集合对象”,也叫做 “容器” “聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。

一个完整的迭代器模式,会设计容器和容器迭代器两部分。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口和迭代器实现类。容器中需要定义 iterator() 方法,用来创建迭代器。迭代器接口中需要定义 hasNext()next()currentItem() 三个最基本的方法。容器对象通过依赖注入传递到迭代器类中。

遍历集合一般有 3 种方式:for 循环、foreach 循环、iterator 迭代器。后两种本质上属于一种,都可以看做迭代器遍历。相对于 for 循环,利用迭代器遍历有下面 3 个优势:

  • 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可。
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。此外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/533085.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Day20_学点儿JavaEE_基于Session的登录、数据库null值正确显示

1 登录 使用Session技术完成用户登录的功能&#xff1a; 登录功能会使用到Session&#xff0c;把用户登录的用户名和密码保存到Session&#xff0c;因为Session是属于每个用户独有的&#xff0c;就可以记录每个用户单独的登录信息。 当然&#xff0c;这仅仅是完成了一个简单的…

windows安装charles抓包iphone

安装charles抓包iphone charles基础介绍windows安装 charles基础介绍 Charles 是在 PC 端常用的网络封包截取工具&#xff0c;在做移动开发时&#xff0c;我们为了调试与服务器端的网络通讯协议&#xff0c;常常需要截取网络封包来分析。除了在做移动开发中调试端口外&#xf…

探索GlusterFS:开源分布式文件系统

目录 引言 一、GlusterFS简介 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;GlusterFS特点 &#xff08;三&#xff09;GlusterFS术语 &#xff08;四&#xff09;GlusterFS工作流程 二、GlusterFs的卷类型 &#xff08;一&#xff09;卷类型 &…

vue3中使用antv-S2表格(基础功能版)

先看展示效果&#xff1a; 可以调整行宽、列宽、自定义字段图标、表头图标、添加排序、显示总计、小计等 首先确保搭建一个vue3项目环境&#xff0c;从0开始的小伙伴着重看第一点&#xff1a; 一、搭建vue3项目环境 首先创建一个vue3vitets项目&#xff0c;可以查看下面相关…

铸造大型基础平板的结构应该怎样设计

设计大型基础平板的结构时&#xff0c;需要考虑以下几个方面&#xff1a; 地质条件&#xff1a;首先要了解工程所在地的地质条件&#xff0c;包括土质、地下水位、地震状况等。根据地质条件来选择合适的基础类型&#xff0c;如浅基、深基或地下连续墙等。 荷载分析&#xff1a…

Lumos学习python第九课:VSCode+Anaconda

注意Anaconda版本和Python版本的对应关系&#xff0c;同一个Anaconda可以支持多个Python版本&#xff0c; 注&#xff1a;现在vscode已原生支持jupyter notebook&#xff08;要求Python版本>3.6&#xff09; Anaconda在Python解析器的基础上封装了很多Python包&#xff0c…

Weblogic任意文件上传漏洞(CVE-2018-2894)漏洞复现(基于vulhub)

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

C++模板编程

模板是泛型编程的基础&#xff0c;先给出泛型编程的概念。 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。 应用场景&#xff1a;比如要实现一个通用的&#xff0c;进行两个变量互相交换的函数&#xff0c;此时可以通过函数重载的方式&…

Ubuntu配置VScode的C++环境

在Ubuntu系统下配置C环境&#xff0c;并运行helloworld 1. 下载VScode 我这里使用的是星火应用商店&#xff0c;在商店里面可以直接下载安装 http://spark-app.store/ 2.创建文件夹 3.启动VScode并打开该文件夹 4.安装以下几个扩展 PS&#xff1a;Clang这个插件别安装&…

3. DAX 时间函数-- DATE 日期--一生二,二生三,三生万物

在数据分析过程中&#xff0c;经常需要从一个数据推到另外一个数据&#xff0c;日期数据也是如此&#xff0c;需要从一个日期推到另外一个相关的日期&#xff0c;或者从一群日期推到另外一个相关的日期/一群相关的日期。这一期说的就是日期之间彼此推衍的函数&#xff0c;会比之…

C# 操作PDF表单 - 创建、填写、删除PDF表单域

通常情况下&#xff0c;PDF文件是不可编辑的&#xff0c;但PDF表单提供了一些可编辑区域&#xff0c;允许用户填写和提交信息。PDF表单通常用于收集信息、反馈或进行在线申请&#xff0c;是许多行业中数据收集和交换的重要工具。 PDF表单可以包含各种类型的输入控件&#xff0…

QT:事件机制

作业&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> #include <QTime> #include<QPushButton> #include <QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAME…

头歌-机器学习 第15次实验 朴素贝叶斯分类器

第1关:条件概率 任务描述 本关任务:根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务,你需要掌握条件概率。 条件概率 朴素贝叶斯分类算法是基于贝叶斯定理与特征条件独立假设的分类方法,因此想要了解朴素贝叶斯分类算法背后的算法原理,就不得…

【CSS】一篇文章讲清楚screen、window和html元素的位置:top、left、width、height

一个Web网页从内到外的顺序是&#xff1a; 元素div,ul,table... → 页面body → 浏览器window → 屏幕screen 分类详情屏幕screen srceen.width - 屏幕的宽度 screen.height - 屏幕的高度&#xff08;屏幕未缩放时&#xff0c;表示屏幕分辨率&#xff09; screen.availLeft …

(一)基于IDEA的JAVA基础13

数组遍历 遍历数组就是把数组内的数据一个个的取出来 1.我们可以用for循环&#xff0c;依次把数字类的元素取出来。 2.增强型for循环。 用第一个方法写一下&#xff0c;看一下 public class Test01 { public static void main(String[] args) { //存储一组数据{…

TQ15EG开发板教程:在MPSOC上运行ADRV9009

首先需要在github上下载两个文件&#xff0c;本例程用到的文件以及最终文件我都会放在网盘里面&#xff0c; 地址放在最后面。在github搜索hdl选择第一个&#xff0c;如下图所示 GitHub网址&#xff1a;https://github.com/analogdevicesinc/hdl/releases 点击releases选择版…

【C++题解】1005 - 已知一个圆的半径,求解该圆的面积和周长

问题&#xff1a;1005 - 已知一个圆的半径&#xff0c;求解该圆的面积和周长 类型&#xff1a;基础问题、小数运算 题目描述&#xff1a; 已知一个圆的半径&#xff0c;求解该圆的面积和周长。 输入&#xff1a; 输入只有一行&#xff0c;只有 1 个整数。 输出&#xff1a…

迭代器模式【行为模式C++】

1.简介 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合&#xff08;聚合对象&#xff09;底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合&#xff08;聚合对象&#xff09;中所有的元素。 迭代器的意义就是将这个行为抽离封装起来&a…

【大厂生产案例】JVM调优

写作目的 最近上线了一个需求&#xff0c;遇到了一个JVM报警的问题&#xff0c;很荣幸能遇到&#xff0c;在此分享一下整个调优的过程。 背景 我们是中台服务&#xff0c;我们的甲方就是上游不同的业务。中台原则上是业务和能力分离&#xff0c;但是不可避免的是分不开&…

数据结构学习之路--全面破解顺序表的奥秘(附C源码)

好久不见啊~大家&#xff0c;今天为大家带来的主题是&#xff1a;顺序表的实现。 目录 前言 一、线性表的定义 二、线性表的顺序结构 1 顺序表的定义 2 顺序表的基本操作 2.1 初始化函数 2.2 顺序表的销毁 2.3 顺序表的扩容 2.4 顺序表的尾插 2.5 顺序表的尾删 …