设计模式系列之迭代器模式

开发 前端
迭代器设计模式在我们业务场景中自己写的代码中 我个人是觉得比较少见的,至少到目前我还没有怎么发现有好的业务场景可以用这个模式,所以这里我就不给大家举例业务代码改造了。

[[406817]]

 Iterator大家应该都很熟悉了,作为Java程序员的我们来说,遍历集合这也是我们刚开始学习Java知识。

遍历集合的方式也有很多,比如for循环、while循环、foreach循环、Iterator等。这里的Iterator就是我们设计模式里面的迭代器模式。

这次要跟大家分享的设计模式就是这迭代器模式,虽然很多语言都直接把Iterator封装到基础工具类中,但是它的特性你都了解吗?

本期大纲

定义

迭代器大家都很熟悉,那么什么叫迭代器?它的目的又是什么呢?

  • 定义:我们可以用相同的方式处理集合,无论它是列表还是数组,它都提供了一种迭代其元素而不用暴露其内部结构的机制,更重要的是,不同的类型的集合都可以使用相同的统一机制,这种机制则被称为 迭代器模式。
  • 目的:提供一种顺序遍历聚合对象元素,而不暴露其内部实现的方法。

以上定义来之设计模式之美

解析图:

  • Aggregate(抽象容器):负责提供创建具体迭代器角色的接口,对应于java.util.Collection接口。
  • Iterator(抽象迭代器):迭代器的抽象类,它定义遍历容器对象的操作以及返回对象的操作
  • ConcreteAggregate(具体容器):主要是可以实现内部不同的结构。但会暴露处理遍历容器的具体迭代器。
  • ConcreteIterator(具体迭代器):处理特定的具体容器类的具体迭代器,实际上对于每个容器具体容器,都必须实现一个具体的迭代器。

整个图看起来其实就两个东西,一个容器,一个迭代器。

代码实现

这次就不举列了。直接手写一个迭代器,我们再测试一下。

主要还是理解迭代器到底是干嘛用的:

  • 能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

话不多说,还是直接上手撸代码

  1. public interface Aggregate { 
  2.     // 添加元素 
  3.     void add(Object object); 
  4.  
  5.     // 移除元素 
  6.     void remove(Object object); 
  7.  
  8.     // 迭代器 
  9.     Iterator iterator(); 

按照上面的类图,先是创建抽象容器,定义几个基本添加删除元素方法,以及迭代器

  1. public interface Iterator<E> { 
  2.     // 判断容器是否有值 
  3.     boolean hasNext(); 
  4.  
  5.     // 把游标执向下一个指针 
  6.     void next(); 
  7.  
  8.     // 当前遍历的数据 
  9.     E currentItem(); 

其次 再试创建抽象迭代器,遍历容器中的数据

  1. public class ConcreteAggregate implements Aggregate { 
  2.     private ArrayList arrayList = new ArrayList(); 
  3.  
  4.     @Override 
  5.     public void add(Object object) { 
  6.         this.arrayList.add(object); 
  7.     } 
  8.  
  9.     @Override 
  10.     public void remove(Object object) { 
  11.         this.arrayList.remove(object); 
  12.     } 
  13.  
  14.     @Override 
  15.     public Iterator iterator() { 
  16.         return new ConcreteIterator(this.arrayList); 
  17.     } 

开始定义我们具体的容器了,内部定一个ArrayList容器,用来存放数据,当然这里大家也可以改成其他的容器 比如说用Vector 或者其他的 栈、 树、 图 等

  1. public class ConcreteIterator<E> implements Iterator<E> { 
  2.  
  3.     private int cursor; // 游标 
  4.     private ArrayList arrayList; 
  5.  
  6.     public ConcreteIterator(ArrayList arrayList) { 
  7.         this.cursor = 0; 
  8.         this.arrayList = arrayList; 
  9.     } 
  10.  
  11.     @Override 
  12.     public boolean hasNext() { 
  13.         if (this.cursor == this.arrayList.size()) { 
  14.             return false
  15.         } 
  16.         return true
  17.     } 
  18.  
  19.     @Override 
  20.     public void next() { 
  21.         cursor++; 
  22.         System.out.println(cursor + "   cursor"); 
  23.     } 
  24.  
  25.     @Override 
  26.     public E currentItem() { 
  27.         if (cursor >= arrayList.size()) { 
  28.             throw new NoSuchElementException(); 
  29.         } 
  30.         E e = (E) arrayList.get(cursor); 
  31.         this.next(); 
  32.         return e; 
  33.     } 
  34.    
  35.    // 测试demo 
  36.     public static void main(String[] args) { 
  37.         Aggregate aggregate = new ConcreteAggregate(); 
  38.         aggregate.add("java"); 
  39.         aggregate.add("c++"); 
  40.         aggregate.add("php"); 
  41.         aggregate.add("敖丙"); 
  42.  
  43.         Iterator iterator = aggregate.iterator(); 
  44.         while (iterator.hasNext()) { 
  45.             System.out.println(iterator.currentItem()); 
  46.         } 
  47.       // 结果:1    java 
  48.       //      2     c++ 
  49.       //      3     php 
  50.       //      4     敖丙 
  51.     } 

最后就是实现具体的迭代器了, 在currentItem里面根据遍历的游标,获取数组里面的值

同时在main方法里面就是测试demo了,以上就是简单的手撸迭代器了。

这里面我们其实还可以有其它的各种特别的玩法,比如说怎么实现暂停遍历等,只有了解内部实现,我们才能改造出符合当前所需要的业务代码。

Java中的迭代器

在Java的中也有迭代器,java.util.Iterator类以及java.util.Collection,就是典型的迭代器喝容器的列子,接下来看看具体的源码

当next没有值的时候则会抛出NoSuchElementException异常信息,上面的手撸异常也是根据这个来的

在Java中 常见的 List、Set、Queue都是extend Collection(容器),而Collection又定义迭代器Iterator,这就是能直接使用的原因了。

Java集合分析

上面我们看完了Java中的迭代器,不知道,大家注意了没有,我们在使用迭代器的时候是不能再对集合进行增减操作的,否则就会抛出ConcurrentModificationException异常

那么问题来了,为什么会有这个异常信息呢?

看过ArrayList源码的同学都知道底层是数据结构中的数组结构的,所以我们看下接下来图结构

假设现在开始在遍历当前这个数组,当从第一步执行到第二步,都是正常运行的,假设现在执行完第二步,开始走第三步时

删除 java这个元素,数组为了保持存储数据的连续性,当删除java数据时,是会发生数组元素的迁移的。所以正常步骤3应该是遍历到aobing元素的变成当前数组元素已经是ao bing了。

导致了数组会发生ao bing没有遍历到,因为数据迁移而丢失了。

同样的假设在后面添加元素按照向后迁移,还能遍历到,那如过插入的数据是在已经遍历的之前呢?

这样整个遍历就变成不可预估了。

  1. public static void main(String[] args) { 
  2.       List<String> aggregate = new ArrayList(); 
  3.       aggregate.add("java"); 
  4.       aggregate.add("c++"); 
  5.       aggregate.add("php"); 
  6.       aggregate.add("敖丙"); 
  7.       Iterator<String> iterator = aggregate.iterator(); 
  8.       while (iterator.hasNext()) { 
  9.           iterator.remove(); // 添加这行代码 java.lang.IllegalStateException 
  10.           System.out.println(iterator.next()); 
  11.           iterator.remove(); // 正常 
  12.       } 
  13.   } 

再来看这个测试demo,同样都是调用remove方法,不同的地方结果不一样,这也就是刚好印证上面的图体现的问题,所以要解决这个问题,要么就是遍历的时候不允许增删元素,要么是增删元素之后让遍历报错。

通过上面的列子已经了解了迭代器的原理以及实现,大家可以根据自己所需要的场景改造迭代器,很多公司的一些自己的框架或者工具类等等都是通过现有框架源码进行改造而来。

迭代器的优点:

  • 迭代器模式封装集合内部的复杂数据结构,不用关心需要遍历的对象。
  • 符合单一职责原则以及开闭原则
  • 可以对遍历进行把控暂停或者继续

总结

迭代器设计模式在我们业务场景中自己写的代码中 我个人是觉得比较少见的,至少到目前我还没有怎么发现有好的业务场景可以用这个模式,所以这里我就不给大家举例业务代码改造了。(毕竟不能因为设计模式而强行设计)

跟大家分享迭代器主要是想让大家了解Java集合遍历怎么实现的,方便我们提升自己以后的看源码的能力,以及提升自己的设计能力。

后面就再跟大家再聊聊动态代理设计模式,就不会再详细讲了其他的模式了,因为本身不怎么常见,作为了解还是会和大家做一个总结分享。

今天的迭代器模式到此结束,我是敖丙,你知道的越多,你不知道的越多,我们下期见!!!

 

责任编辑:姜华 来源: 三太子敖丙
相关推荐

2020-11-06 09:01:46

迭代器模式

2010-04-29 08:53:11

PHP迭代器模式

2021-06-09 08:53:34

设计模式策略模式工厂模式

2012-01-13 15:59:07

2020-11-09 08:20:33

解释器模式

2021-03-05 07:57:41

设计模式桥接

2021-01-21 05:34:14

设计模式建造者

2023-09-04 13:14:00

装饰器设计模式

2022-01-19 08:21:12

设计装饰器模式

2021-07-08 11:28:43

观察者模式设计

2020-05-25 10:20:19

享元模式场景

2021-02-18 08:39:28

设计模式场景

2023-12-13 13:28:16

装饰器模式Python设计模式

2022-01-12 13:33:25

工厂模式设计

2010-04-21 08:38:18

解释器模式PHP设计模式

2020-10-23 09:40:26

设计模式

2020-11-04 08:54:54

状态模式

2020-11-03 13:05:18

命令模式

2021-09-29 13:53:17

抽象工厂模式

2021-03-02 08:50:31

设计单例模式
点赞
收藏

51CTO技术栈公众号