16.迭代器模式设计思想

16.迭代器模式设计思想

目录介绍

  • 01.迭代器模式基础
    • 1.1 迭代器模式由来
    • 1.2 迭代器模式定义
    • 1.3 迭代器模式场景
    • 1.4 迭代器模式思考
    • 1.5 主要解决的问题
  • 02.迭代器模式原理
    • 2.1 罗列一个场景
    • 2.2 用例子理解迭代器
    • 2.3 案例实现过程
    • 2.4 迭代器实现步骤
  • 03.迭代器模式结构
    • 3.1 迭代器标准案例
    • 3.2 迭代器模式结构图
    • 3.3 迭代器模式时序图
  • 04.迭代器模式案例
    • 4.1 Java中Iterator接口
    • 4.2 迭代器遍历案例
    • 4.3 遍历时不建议删除元素
    • 4.4 for循环VS迭代器
    • 4.5 迭代器原理介绍
    • 4.6 迭代器模式优势
  • 05.迭代器者模式分析
    • 5.1 迭代器模式优点
    • 5.2 迭代器模式缺点
    • 5.3 适用环境
    • 5.4 思考题考察
  • 06.观察者模式总结
    • 6.1 总结一下学习
    • 6.2 更多内容推荐

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

https://yccoding.com/

关于设计模式,所有的代码都放到了该项目。设计模式大全

01.迭代器模式基础

1.1 迭代器模式由来

容器的种类有很多种,比如ArrayList、LinkedList、HashSet...

  1. 每种容器都有自己的特点,ArrayList底层维护的是一个数组;LinkedList是链表结构的;HashSet依赖的是哈希表,每种容器都有自己特有的数据结构。
  2. 因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java引入了迭代器模式!

不同的集合会对应不同的遍历方法,客户端代码无法复用。在实际应用中如何将两个不同集合整合是相当麻烦的。

所以才有Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。

1.2 迭代器模式定义

迭代器模式为遍历集合中的元素提供了一种通用接口。

其核心思想是,将集合的遍历逻辑与集合对象的实现分离。通过迭代器,客户端代码无需关心集合的底层实现方式(如数组、链表等),就能顺序地访问集合的每一个元素。

1.3 迭代器模式场景

典型场景如下所示:

  1. Java 集合框架:Java 的 Iterator 接口是迭代器模式的一个经典实现。诸如 List、Set 等集合类都支持使用迭代器来遍历元素。
  2. 文件处理:处理文件中的多行内容时,可以使用迭代器模式来顺序读取每一行,而不需要在意底层文件如何存储。
  3. 数据库结果集遍历:数据库查询返回的结果集可以通过迭代器模式逐条访问,不必关心结果集的存储细节。

1.4 迭代器模式思考

以下是一些关于迭代器模式的思考:

  1. 遍历过程中的修改:在使用迭代器遍历集合时,如果需要对集合进行修改,可能会引发一些问题。
  2. 封装集合:迭代器模式允许将集合的内部实现细节隐藏起来,只暴露一个统一的迭代器接口。

1.5 主要解决的问题

迭代器模式主要解决以下问题:

  1. 隐藏集合的内部结构:迭代器模式允许客户端代码遍历集合中的元素,而无需了解集合的内部结构。
  2. 统一遍历接口:迭代器模式定义了一个统一的迭代器接口,提高代码复用性和可读性。
  3. 安全地遍历集合:迭代器模式可以确保在遍历集合时不会对集合的结构造成破坏。
  4. 支持并发遍历:在多线程环境下,使用迭代器模式可以支持并发遍历集合。

02.迭代器模式原理

2.1 罗列一个场景

用 Java 代码实现一个简单的迭代器模式示例,场景为遍历一个字符串集合。

2.2 用例子理解迭代器

2.3 案例实现过程

1 迭代器接口,定义了遍历集合的方法

interface Iterator<T> {
    boolean hasNext();
    T next();
}

2 集合接口,定义了创建迭代器的方法

interface Aggregate<T> {
    Iterator<T> createIterator();
}

3 具体的集合类,包含一个字符串数组

class ConcreteAggregate implements Aggregate<String> {
    private String[] items;
    private int size;
 
    public ConcreteAggregate(int size) {
        this.size = size;
        items = new String[size];
        for (int i = 0; i < size; i++) {
            items[i] = "Item " + (i + 1);
        }
    }
 
    // 创建具体的迭代器
    @Override
    public Iterator<String> createIterator() {
        return new ConcreteIterator(this);
    }
 
    // 获取集合的大小
    public int getSize() {
        return size;
    }
 
    // 通过索引获取集合中的元素
    public String getItem(int index) {
        if (index < size) {
            return items[index];
        }
        return null;
    }
}

4 具体的迭代器类,负责遍历字符串集合

class ConcreteIterator implements Iterator<String> {
    private ConcreteAggregate aggregate;
    private int currentIndex = 0;
 
    public ConcreteIterator(ConcreteAggregate aggregate) {
        this.aggregate = aggregate;
    }
 
    // 判断是否还有下一个元素
    @Override
    public boolean hasNext() {
        return currentIndex < aggregate.getSize();
    }
 
    // 获取下一个元素
    @Override
    public String next() {
        return aggregate.getItem(currentIndex++);
    }
}

5 测试迭代器模式

public class IteratorPatternDemo {
    public static void main(String[] args) {
        // 创建具体集合
        ConcreteAggregate aggregate = new ConcreteAggregate(5);
 
        // 创建迭代器
        Iterator<String> iterator = aggregate.createIterator();
 
        // 使用迭代器遍历集合
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

ConcreteAggregate:表示一个具体的集合,内部存储若干个字符串,并且实现了 Aggregate 接口,提供创建迭代器的功能。

ConcreteIterator:实现 Iterator 接口,内部持有对集合的引用,负责遍历集合中的元素。

IteratorPatternDemo:测试类,创建集合对象,并使用迭代器逐一访问集合中的元素。

2.4 迭代器实现步骤

  1. Iterator(迭代器接口):定义了访问集合元素的方法。通常包括以下常见操作:
    • hasNext():检查是否还有下一个元素。
    • next():返回集合中的下一个元素。
    • remove():删除当前元素(可选)。
  2. ConcreteIterator(具体迭代器类):实现 Iterator 接口,负责遍历具体集合中的元素。
  3. Aggregate(集合接口):定义创建迭代器的接口。集合类通常提供一个 createIterator() 方法,用于返回一个 Iterator 对象。
  4. ConcreteAggregate(具体集合类):实现集合接口,包含若干元素,并能够生成相应的迭代器对象。
  5. Client(客户端):使用迭代器的地方。

03.迭代器模式结构

3.1 迭代器标准案例

3.2 迭代器模式结构图

角色职责说明

  1. Aggregate:定义了创建迭代器的接口,负责管理集合中的元素。
  2. ConcreteAggregate:具体的集合类,实现了创建迭代器的接口,并持有具体的数据。
  3. Iterator:定义了用于遍历集合的方法。
  4. ConcreteIterator:负责具体的迭代操作,内部维护一个指针来跟踪当前访问的元素。

3.3 迭代器模式时序图

04.迭代器模式案例

4.1 Java中Iterator接口

看看Java中的Iterator接口是如何实现的。 在Java中Iterator为一个接口,它只提供了迭代的基本规则。

package java.util;
public interface Iterator<E> {
    boolean hasNext();//判断是否存在下一个对象元素
    E next();//获取下一个元素
    void remove();//移除元素
}

Iterable,Java中还提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器

我们常用的实现了该接口的子接口有:Collection、List、Set等。该接口的iterator()方法返回一个标准的Iterator实现。实现Iterable接口允许对象成为Foreach语句的目标。就可以通过foreach语句来遍历你的底层序列。

Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。

Iterable接口的具体实现:

import java.util.Iterator;
public interface Iterable<T> {
    Iterator<T> iterator();
}

4.2 迭代器遍历案例

如果要问Java中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式,先来看一段代码吧:

public static void print(Collection coll){  
    Iterator it = coll.iterator();  
    while(it.hasNext()){  
        String str = (String)it.next();  
        System.out.println(str);  
    }  
}

这个方法的作用是循环打印一个字符串集合,里面就用到了迭代器模式

java语言已经完整地实现了迭代器模式,例如List,Set,Map,而迭代器的作用就是把容器中的对象一个一个地遍历出来。

4.3 遍历时不建议删除元素

Iterator遍历时不可以删除集合中的元素问题。在使用Iterator的时候禁止对所遍历的容器进行改变其大小结构的操作。

例如:在使用Iterator进行迭代时,如果对集合进行了add、remove操作就会出现ConcurrentModificationException异常。

在你迭代之前,迭代器已经被通过list.iterator()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,那么Java就会给出异常。

因为此时Iterator对象已经无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出ConcurrentModificationException异常)

List<String> list = new ArrayList<String>();
list.add("张三1");
list.add("张三2");
list.add("张三3");
list.add("张三4");
//……
list.add("张三N");

//使用迭代器遍历ArrayList集合
Iterator<String> listIt = list.iterator();
while(listIt.hasNext()){
    Object obj = listIt.next();
    if(obj.equals("张三100")){
        list.remove(obj);
    }
}

看一下ArrayList中的源码,看看迭代器中next和remove的实现方法代码。

public Iterator<E> iterator() {
    return new Itr();
}

/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    // Android-changed: Add "limit" field to detect end of iteration.
    // The "limit" of this iterator. This is the size of the list at the time the
    // iterator was created. Adding & removing elements will invalidate the iteration
    // anyway (and cause next() to throw) so saving this value will guarantee that the
    // value of hasNext() remains stable and won't flap between true and false when elements
    // are added and removed from the list.
    protected int limit = ArrayList.this.size;

    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor < limit;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        int i = cursor;
        if (i >= limit)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
            limit--;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;

        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

查看源码发现原来检查并抛出异常的是 checkForComodification() 方法。在ArrayList中modCount是当前集合的版本号,每次修改(增、删)集合都会加1;expectedModCount是当前迭代器的版本号,在迭代器实例化时初始化为modCount。

我们看到在checkForComodification()方法中就是在验证modCount的值和expectedModCount的值是否相等,所以当你在调用了ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,而迭代器中的expectedModCount未同步,因此才会导致再次调用Iterator.next()方法时抛出异常。

但是为什么使用Iterator.remove()就没有问题呢?通过源码的第32行发现,在Iterator的remove()中同步了expectedModCount的值,所以当你下次再调用next()的时候,检查不会抛出异常。

使用该机制的主要目的是为了实现ArrayList中的快速失败机制(fail-fast),在Java集合中较大一部分集合是存在快速失败机制的。

快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常

所以要保证在使用Iterator遍历集合的时候不出错误,就应该保证在遍历集合的过程中不会对集合产生结构上的修改。

4.4 for循环VS迭代器

for循环与迭代器的对比,效率上各有各的优势:

  1. ArrayList对随机访问比较快,而for循环中使用的get()方法,采用的即是随机访问的方法,因此在ArrayList里for循环快。
  2. LinkedList则是顺序访问比较快,Iterator中的next()方法采用的是顺序访问方法,因此在LinkedList里使用Iterator较快。

4.5 迭代器原理介绍

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

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

迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

迭代器是用来遍历容器的

所以,一个完整的迭代器模式一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。

4.6 迭代器模式优势

一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。对于这三种方式,我拿 Java 语言来举例说明一下。具体的代码如下所示:

List<String> names = new ArrayList<>();
names.add("xzg");
names.add("wang");
names.add("zheng");

// 第一种遍历方式: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.print(iterator.next() + ",");//Java中的迭代器接口是第二种定义方式,next()既移动游标又返回数据
}

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

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

  1. 首先,对于类似数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。
  2. 其次,将游标指向的当前位置等信息,存储在迭代器类中,每个迭代器独享游标信息。这样,我们就可以创建多个不同的迭代器,同时对同一个容器进行遍历而互不影响。
  3. 最后,容器和迭代器都提供了抽象的接口,方便我们在开发的时候,基于接口而非具体的实现编程。当需要切换新的遍历算法的时候,比如,从前往后遍历链表切换成从后往前遍历链表,客户端代码只需要将迭代器类从 LinkedIterator 切换为 ReversedLinkedIterator 即可,其他代码都不需要修改。除此之外,添加新的遍历算法,我们只需要扩展新的迭代器类,也更符合开闭原则。

05.迭代器者模式分析

5.1 迭代器模式优点

  1. ①简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
  2. ②可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
  3. ③封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。

5.2 迭代器模式缺点

  1. 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像ArrayList,我们宁可愿意使用for循环和get方法来遍历集合。

5.3 适用环境

  1. 需要顺序访问集合对象的元素,但又不想暴露集合的底层实现。比如:有时我们希望从列表、栈、队列等数据结构中提取数据,但不希望直接操作这些数据结构的内部。
  2. 支持多种遍历方式。迭代器模式不仅仅能实现前向遍历,还可以实现其他方式的遍历,如双向遍历、逆序遍历等。
  3. 希望为不同的集合对象提供统一的遍历接口。无论是链表、数组,还是自定义的复杂数据结构,迭代器模式可以提供一种统一的遍历接口。

5.4 思考题考察

06.观察者模式总结

6.1 总结一下学习

迭代器模式,也叫游标模式。它用来遍历集合对象。这里说的“集合对象”,我们也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。

一个完整的迭代器模式,一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。

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

6.2 更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android

23种设计模式

23种设计模式 & 描述 & 核心作用 包括
创建型模式
提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式
关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式
特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

6.3 更多内容

posted @ 2024-11-25 09:33  潇湘剑雨yc  阅读(15)  评论(0编辑  收藏  举报