如何徹底搞懂迭代器(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.pswp.cn/web/13600.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13600.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13600.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

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

本篇會加入個人的所謂魚式瘋言 ??????魚式瘋言:??????此瘋言非彼瘋言 而是理解過并總結出來通俗易懂的大白話, 小編會盡可能的在每個概念后插入魚式瘋言,幫助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能說的不是那么嚴謹.但小編初心是能讓更多人…

removeAttribute和removeAttributeNode有什么區別(代碼舉例說明)

removeAttribute 和 removeAttributeNode 都是用于從 HTML 元素中移除屬性的 DOM 方法&#xff0c;但它們在用法和接受的參數上有一些區別。 removeAttribute removeAttribute 是一個元素&#xff08;Element&#xff09;對象的方法&#xff0c;它接受一個字符串參數&#xf…

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

一、Nginx核心原理 本節為大家介紹Nginx的核心原理,包含Reactor模型、Nginx的模塊化設計、Nginx的請求處理階段. &#xff08;本文源自微博客,且已獲得授權&#xff09; 1.1、Reactor模型 Nginx對高并發IO的處理使用了Reactor事件驅動模型。Reactor模型的基本組件包含時間收集…

華為OBS命令行簡單使用

華為OBS&#xff08;Object Storage Service&#xff09;是一種云存儲服務&#xff0c;提供了高可靠、高性能、安全的數據存儲能力。通過使用OBS的命令行工具obsutil&#xff0c;用戶可以方便地進行文件上傳、下載、刪除等操作&#xff0c;而無需依賴圖形界面。下面&#xff0c…

使用xsd驗證xml格式的正確性

1.1 基礎知識介紹 XML簡介&#xff1a;XML是可擴展標記語言&#xff08;eXtensible Markup Language&#xff09;的縮寫&#xff0c;它是一種數據表示格式&#xff0c;可以描述非常復雜的數據結構&#xff0c;常用于傳輸和存儲數據。xml文件、xml消息。XSD簡介&#xff1a;是X…

oracle 表同一列只取最新一條數據寫法

select * from (select t.*,row_number() over(partition by 去重列名 order by 排序列名 desc) as rnfrom 表名)where rn1 1.row_number() over(....): 為每條數據分配一個行號,1.2.3....這樣的 2.partition by : 以某列作為分組&#xff0c;每個分組行號從1開始&#xf…

ComputerLab實例2.0(繼承)

要求&#xff1a; Write a computer program that could be used to track users activities. Lab NumberComputer Station Numbers11-321-431-541-6 ? You run four computer labs. Each lab contains computer stations that are numbered as the above table. ? There…

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框…

NB49 牛群的秘密通信

描述 在一個遠離人類的世界中&#xff0c;有一群牛正在進行秘密通信。它們使用一種特殊的括號組合作為加密通信的形式。每一組加密信息均包括以下字符&#xff1a;(,{,[,),},]。 加密信息需要滿足以下有效性規則&#xff1a; 每個左括號必須使用相同類型的右括號閉合。左括號…

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…

OrangePiKunPengPro | 開發板學習與使用

OrangePi KunPengPro | 開發板學習與使用 時間:2024年5月23日20:51:12 文章目錄 `OrangePi KunPengPro` | 開發板學習與使用1.參考2.資料2.使用2-1.通過串口登錄系統2-2.通過SSH登錄系統2-3.安裝交叉編譯工具鏈2-4.復制文件到設備1.參考 1.OrangePi Kunpeng Pro Orange Pi官網…

c語言指針入門(二)

今天學習了指針的兩個常用場景&#xff0c;在此記錄&#xff0c;以便后續查看。 場景1&#xff1a;傳數組 在c語言中&#xff0c;我們在定義函數的時候是沒有辦法直接傳一個數組進去的&#xff0c;為了解決這個問題&#xff0c;我們一般將數組的名稱當作一個指針參數傳入到函數…

mysql主從復制的步驟和使用到的操作命令有哪些?

步驟&#xff1a; 配置主服務器&#xff08;Master&#xff09;&#xff1a; 啟用二進制日志記錄&#xff08;binary logging&#xff09;。配置主服務器的唯一標識&#xff08;server-id&#xff09;。創建用于復制的專用復制賬戶。 配置從服務器&#xff08;Slave&#xff0…

安裝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 …

【15】編寫shell-安裝mysql

說明: 1、請注意mysql版本的壓縮包格式 2、請注意掛載data盤 3、請注意部署包和shell腳本放在同一個文件夾 4、實現shell腳本自動化部署mysql5.7.40版本 # !/bin/bash#****************************************************** # Author : 秋天楓葉35 # Last modified …