如何彻底搞懂迭代器(Iterator)设计模式?

说起迭代器(Iterator),相信你并不会陌生,因为我们几乎每天都在使用JDK中自带的各种迭代器。那么,这些迭代器是如何构建出来的呢?就需要用到了今天内容要介绍的迭代器设计模式。在日常开发过程中,我们可能很少会自己去实现一个迭代器,但掌握迭代器设计模式对于我们学习一些开源框架的源码还是很有帮助的,因为在像Mybatis等主流开发框架中都用到了迭代器模式。

迭代器设计模式的概念和简单示例

在对迭代器模式的应用场景和方式进行展开之前,让我们先来对它的基本结构做一些展开。迭代器是这样一种结构:它提供一种方法,可以顺序访问聚合对象中的各个元素,但又不暴露该对象的内部表示。

想要构建这样一个迭代器,我们就可以引入迭代器设计模式。迭代器模式的基本结构如下图所示。


上图中的Aggregate相当于是一个容器,致力于提供符合Iterator实现的数据格式。当我们访问容器时,则是使用Iterator提供的数据遍历方法进行数据访问,这样处理容器数据的逻辑就和容器本身的实现了解耦,因为我们只需要使用Iterator接口就行了,完全不用关心容器怎么实现、底层数据如何访问之类的问题。而且更换容器的时候也不需要修改数据处理逻辑。

明白了迭代器模式的基本结构,接下来我们来给出对应的案例代码。首当其冲的,我们需要实现一个Iterator接口,如下所示。

public interface Iterator<T> {

//是否存在下一个元素

  boolean hasNext();

//获取下一个元素

  T next();

}

注意到这里使用的泛型结构,意味着这个迭代器接口可以应用到各种数据结构上。而这里的hasNext和next方法分别用来判断迭代器中是否存在下一个元素,以及下一个元素具体是什么。

然后,我们可以创建一个代表元素的数据结构,例如像这样的Item类。

public class Item {

  private ItemType type;

  private final String name;

  public Item(ItemType type, String name) {

    this.setType(type);

    this.name = name;

  }

}

注意到这里包含了两个参数,一个是ItemType枚举,代表Item的类型,另一个则指定Item的名称。

如果我们把Item看做是一个个宝物,那么我们就可以构建一个宝箱(TreasureChest)类,

public class TreasureChest {

  private final List<Item> items;

  

  public TreasureChest() {

    items = List.of(

        new Item(ItemType.POTION, "勇气药剂"),

        new Item(ItemType.RING, "阴影之环"),

        new Item(ItemType.POTION, "智慧药剂"),

        new Item(ItemType.WEAPON, "银色之剑"),

        new Item(ItemType.POTION, "腐蚀药剂"),

        new Item(ItemType.RING, "盔甲之环"),

        new Item(ItemType.WEAPON, "毒之匕首"));

  }

  public Iterator<Item> iterator(ItemType itemType) {

    return new TreasureChestItemIterator(this, itemType);

  }

  public List<Item> getItems() {

    return new ArrayList<>(items);

  }

}

结合迭代器模式的基本结构,这个TreasureChest类相当于就是代表容器的Aggregate类,该类依赖于Iterator接口,同时又负责创建一个迭代器组件TreasureChestItemIterator。TreasureChestItemIterator类如下所示。

public class TreasureChestItemIterator implements Iterator<Item> {

//当前项索引

  private int idx;

  private final TreasureChest chest;

  private final ItemType type;

  public TreasureChestItemIterator(TreasureChest chest, ItemType type) {

    this.chest = chest;

    this.type = type;

    this.idx = -1;

  }

  @Override

  public boolean hasNext() {

    return findNextIdx() != -1;

  }

  @Override

  public Item next() {

    idx = findNextIdx();

    if (idx != -1) {

      return chest.getItems().get(idx);

    }

    return null;

  }

//寻找下一个Idx

  private int findNextIdx() {

    var items = chest.getItems();

    var tempIdx = idx;

    while (true) {

      tempIdx++;

      if (tempIdx >= items.size()) {

        tempIdx = -1;

        break;

      }

      if (type.equals(ItemType.ANY) || items.get(tempIdx).getType().equals(type)) {

        break;

      }

    }

    return tempIdx;

  }

}

TreasureChestItemIterator的实现主要就是基于当前项索引对Item进行动态遍历和判断。

案例的最后,我们可以构建一段测试代码完成对TreasureChest和TreasureChestItemIterator功能的验证,如下所示。

  private static final TreasureChest TREASURE_CHEST = new TreasureChest();

var itemIterator = TREASURE_CHEST.iterator(ItemType.RING);

    while (itemIterator.hasNext()) {

      LOGGER.info(itemIterator.next().toString());

}

执行这段代码,不难想象我们可以得到如下所示的结果。

阴影之环

盔甲之环

显然,我们获取了对应类型的Item数据,而这个过程对于测试代码而言是完全解耦的,我们不需要知道迭代器内部的运行原理,而只需要关注所返回的结果。

迭代器设计模式在Mybatis中的应用

介绍完迭代器模式的基本概念和代码示例,我们进一步来看看它是如何在主流开源框架中进行应用的。在Mybatis中,针对SQL中配置项语句的解析,专门设计并实现了一套迭代器组件。

Mybatis中存在两个类,通过了对迭代器模式的具体实现,分别是PropertyTokenizer和CursorIterator。我们先来看PropertyTokenizer的实现方法。

PropertyTokenizer

在Mybatis中,存在一个非常常用的工具类PropertyTokenizer,该类主要用于解析诸如“order[0].address.contactinfo.name”类型的属性表达式,在这个例子中,我们可以看到系统是在处理订单实体的地址信息,Mybatis支持使用这种形式的表达式来获取最终的“name”属性。我们可以想象一下,当我们想要解析“order[0].address.contactinfo.name”字符串时,我们势必需要先对其进行分段处理以分别获取各个层级的对象属性名称,如果遇到“[]”符号表示说明要处理的是一个对象数组。这种分层级的处理方式可以认为是一种迭代处理方式,作为迭代器模式的实现,PropertyTokenizer对这种处理方式提供了支持,该类代码如下所示。

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {

  private String name;

  private final String indexedName;

  private String index;

  private final String children;

  public PropertyTokenizer(String fullname) {

    int delim = fullname.indexOf('.');

    if (delim > -1) {

      name = fullname.substring(0, delim);

      children = fullname.substring(delim + 1);

    } else {

      name = fullname;

      children = null;

    }

    indexedName = name;

    delim = name.indexOf('[');

    if (delim > -1) {

      index = name.substring(delim + 1, name.length() - 1);

      name = name.substring(0, delim);

    }

  }

 …

  @Override

  public boolean hasNext() {

    return children != null;

  }

  @Override

  public PropertyTokenizer next() {

    return new PropertyTokenizer(children);

  }

  @Override

  public void remove() {

    throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");

  }

}

针对“order[0].address.contactinfo.name”字符串,当启动解析时,PropertyTokenizer类的name字段指的就是“order”,indexedName字段指的就是“order[0]”,index字段指的就是“0”,而children字段指的就是“address.contactinfo.name”。在构造函数中,当对传入的字符串进行处理时,通过“.”分隔符将其分作两部分。然后在对获取的name字段提取“[”,把中括号里的数字给解析出来,如果name段子你中包含“[]”的话,分别获取index字段并更新name字段。

通过构造函数对输入字符串进行处理之后,PropertyTokenizer的next()方法非常简单,直接再通过children字段再来创建一个新的PropertyTokenizer实例即可。而经常使用的hasNext()方法实现也很简单,就是判断children属性是否为空。

我们再来看PropertyTokenizer类的使用方法,我们在org.apache.ibatis.reflection包的MetaObject类中找到了它的一种常见使用方法,代码如下所示。

public Object getValue(String name) {

    PropertyTokenizer prop = new PropertyTokenizer(name);

    if (prop.hasNext()) {

      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());

      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {

        return null;

      } else {

        return metaValue.getValue(prop.getChildren());

      }

    } else {

      return objectWrapper.get(prop);

    }

  }

这里可以明显看到通过PropertyTokenizer 的prop.hasNext()方法进行递归调用的代码处理流程。

CursorIterator

其实,迭代器模式有时还被称为是游标(Cursor)模式,所以通常可以使用该模式构建一个基于游标机制的组件。我们数据库访问领域中恰恰就有一个游标的概念,当查询数据库返回大量的数据项时可以使用游标Cursor,利用其中的迭代器可以懒加载数据,避免因为一次性加载所有数据导致内存奔溃。而Mybatis又是一个数据库访问框架,那么在这个框架中是否存在一个基于迭代器模式的游标组件呢?答案是肯定的,让我们来看一下。

Mybatis提供了Cursor接口用于表示游标操作,该接口位于org.apache.ibatis.cursor包中,定义如下所示。

public interface Cursor<T> extends Closeable, Iterable<T> {

  boolean isOpen();

  boolean isConsumed();

  int getCurrentIndex();

}

同时,Mybatis为Cursor接口提供了一个默认实现类DefaultCursor,核心代码如下。

public class DefaultCursor<T> implements Cursor<T> {

  private final CursorIterator cursorIterator = new CursorIterator();

  @Override

  public boolean isOpen() {

    return status == CursorStatus.OPEN;

  }

  @Override

  public boolean isConsumed() {

    return status == CursorStatus.CONSUMED;

  }

  @Override

  public int getCurrentIndex() {

    return rowBounds.getOffset() + cursorIterator.iteratorIndex;

  }

// 省略其他方法    

}

我们看到这里引用了CursorIterator,从命名上就可以看出这是一个迭代器,其代码如下所示。

private class CursorIterator implements Iterator<T> {

    T object;

    int iteratorIndex = -1;

    @Override

    public boolean hasNext() {

      if (object == null) {

        object = fetchNextUsingRowBound();

      }

      return object != null;

    }

    @Override

    public T next() {

      // Fill next with object fetched from hasNext()

      T next = object;

      if (next == null) {

        next = fetchNextUsingRowBound();

      }

      if (next != null) {

        object = null;

        iteratorIndex++;

        return next;

      }

      throw new NoSuchElementException();

    }

    @Override

    public void remove() {

      throw new UnsupportedOperationException("Cannot remove element from Cursor");

    }

}

上述游标迭代器CursorIterator实现了java.util.Iterator 迭代器接口,这里的迭代器模式实现方法实际上跟 ArrayList 中的迭代器几乎一样。

对于系统中具有对元素进行迭代访问的应用场景而言,迭代器设计模式能够帮助我们构建优雅的迭代操作。现实中有数据访问方式都与迭代器相关,通过迭代器模式可以构建出灵活而高效的迭代器组件。在今天的内容中,我们通过详细的示例代码对这一设计模式的基本结构进行了展开,并分析了它在Mybatis框架中的两处具有代表性的应用场景以及实现方式。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

查找效率满分的算法—— “二分查找” 算法 (Java版)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

深入了解Nginx(一):Nginx核心原理

一、Nginx核心原理 本节为大家介绍Nginx的核心原理,包含Reactor模型、Nginx的模块化设计、Nginx的请求处理阶段. &#xff08;本文源自微博客,且已获得授权&#xff09; 1.1、Reactor模型 Nginx对高并发IO的处理使用了Reactor事件驱动模型。Reactor模型的基本组件包含时间收集…

使用xsd验证xml格式的正确性

1.1 基础知识介绍 XML简介&#xff1a;XML是可扩展标记语言&#xff08;eXtensible Markup Language&#xff09;的缩写&#xff0c;它是一种数据表示格式&#xff0c;可以描述非常复杂的数据结构&#xff0c;常用于传输和存储数据。xml文件、xml消息。XSD简介&#xff1a;是X…

LabVIEW和ZigBee无线温湿度监测

LabVIEW和ZigBee无线温湿度监测 随着物联网技术的迅速发展&#xff0c;温湿度数据的远程无线监测在农业大棚、仓库和其他需环境控制的场所变得日益重要。开发了一种基于LabVIEW和ZigBee技术的多区域无线温湿度监测系统。系统通过DHT11传感器收集温湿度数据&#xff0c;利用Zig…

uniapp-自定义navigationBar

封装导航栏自定义组件 创建 nav-bar.vue <script setup>import {onReady} from dcloudio/uni-appimport {ref} from vue;const propsdefineProps([navBackgroundColor])const statusBarHeight ref()const navHeight ref()onReady(() > {uni.getSystemInfo({success…

图生代码,从Hello Onion 代码开始

从Hello Onion 代码开始 1&#xff0c;从代码开始 原生语言采用java 作为载体。通过注解方式实现“UI可视化元素"与代码bean之间的映射. 转换示例 2&#xff0c;运行解析原理 在执行JAVA代码期间&#xff0c;通过读取注解信息&#xff0c;转换为前端的JSON交由前端JS框…

c++设计模式-->访问者模式

#include <iostream> #include <string> #include <memory> using namespace std;class AbstractMember; // 前向声明// 行为基类 class AbstractAction { public:virtual void maleDoing(AbstractMember* member) 0;virtual void femaleDoing(AbstractMemb…

安装Pnetcdf顺便升级autoconf与automake

Netcdf NetCDF&#xff08;Network Common Data Form&#xff09;是一种用于存储科学数据的文件格式和软件库。它是一种自描述、可移植且可扩展的数据格式&#xff0c;广泛应用于气象学、海洋学、地球科学和其他领域的科学研究。 NetCDF文件以二进制形式存储&#xff0c;结构…

Qt | QGridLayout 类(网格布局)

01、上节回顾 Qt | QBoxLayout 及其子类(盒式布局)02、QGridLayout 简介 1、网格布局原理(见下图): 基本原理是把窗口划分为若干个单元格,每个子部件被放置于一个或多个单元格之中,各 单元格的大小可由拉伸因子和一行或列中单元格的数量来确定,若子部件的大小(由 sizeH…

Vue从入门到实战 Day08~Day10

智慧商城项目 1. 项目演示 目标&#xff1a;查看项目效果&#xff0c;明确功能模块 -> 完整的电商购物流程 2. 项目收获 目标&#xff1a;明确做完本项目&#xff0c;能够收获哪些内容 3. 创建项目 目标&#xff1a;基于VueCli自定义创建项目架子 4. 调整初始化目录 目…

网络安全之BGP详解

BGP&#xff1b;边界网关协议 使用范围&#xff1b;BGP范围&#xff0c;在AS之间使用的协议。 协议的特点&#xff08;算法&#xff09;&#xff1a;路径矢量型&#xff0c;没有算法。 协议是否传递网络掩码&#xff1a;传递网络掩码&#xff0c;支持VLSM&#xff0c;CIDR …

ASP+ACCESS基于B2C电子商务网站设计

摘 要 运用ASP技术结合了Access数据库原理&#xff0c;基于B/S模式我们开发了一个网上购物系统。在我们的系统中&#xff0c;顾客可以很方便的注册成为会员&#xff0c;对商品进行浏览检索&#xff0c;查看商品的详细资料&#xff0c;然后根据各人的喜好购买心仪的商品。系统…

CCF20220901——如此编码

CCF20220901——如此编码 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,m,cnt1,a[1000],c[1000]{1};cin>>n>>m;for(int i1;i<n;i){cin>>a[i];cnt*a[i];c[i]cnt;}int b[1000]{0};for(int i1;i<n;i)b[i](…

JPHS-JMIR Public Health and Surveillance

文章目录 一、期刊简介二、征稿信息三、期刊表现四、投稿须知五、投稿咨询 一、期刊简介 JMIR Public Health and Surveillance是一本多学科期刊&#xff0c;专注于公共卫生创新与技术的交叉领域&#xff0c;包括公共卫生信息学、监测&#xff08;监测系统和快速报告&#xff…

CCF20220601——归一化处理

CCF20220601——归一化处理 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n,a[1000],sum0;scanf("%d",&n);for(int i1;i<n;i){scanf("%d",&a[i]);suma[i];}double aver1.0,b0.0,d1.0;aversum/(n*1…

Java基础(三)- 多线程、网络通信、单元测试、反射、注解、动态代理

多线程基础 线程&#xff1a;一个程序内部的一条执行流程&#xff0c;只有一条执行流程就是单线程 java.lang.Thread代表线程 主线程退出&#xff0c;子线程存在&#xff0c;进程不会退出 可以使用jconsole查看 创建线程 有多个方法可以创建线程 继承Thread类 优点&#x…

【学习】实验室服务器常用的Linux指令。

1. 下载GitHub代码。 使用代码&#xff1a; git clone https://github.com/Turoad/CLRNet.git2. 压缩 / 解压。 打包压缩 是日常工作中备份文件的一种方式 在不同操作系统中&#xff0c;常用的打包压缩方式是不同的选项 含义 Windows 常用 rarMac 常用 zipLinux 常用 tar.gz…

学硕都考11408的211院校!河北工业大学计算机考研考情分析!

河北工业大学&#xff08;Hebei University of Technology&#xff09;&#xff0c;简称河北工大&#xff0c;坐落于天津市&#xff0c;由河北省人民政府、天津市人民政府与中华人民共和国教育部共建&#xff0c; 隶属于河北省&#xff0c;是国家“双一流”建设高校、国家“211…

自动化测试在软件开发生命周期中如何提高代码质量?

自动化测试是一种在软件开发生命周期中使用软件工具来执行测试的方法&#xff0c;它可以大大提高代码质量&#xff0c;减少开发过程中的错误和缺陷。本文将从零开始&#xff0c;详细且规范地介绍如何使用自动化测试来提高代码质量。 第一步&#xff1a;明确测试目标 在开始自…

webgl入门-绘制三角形

绘制三角形 前言 三角形是一个最简单、最稳定的面&#xff0c;webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 课堂目标 理解多点绘图原理。可以绘制三角形&#xff0c;并将其组合成多边形。 知识点 缓冲区对象点、线、面图形 第一章 web…