迭代器模式 ( Iterator Pattern )

  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. Java for 、for each 循环深入理解(转载)
  3. Java for-each循环解惑
  4. java中为什么要使用Iterator

设计模式用前须知

  • 设计模式种一句出现频率非常高的话是,“ 在不改动。。。。的情况下, 实现。。。。的扩展“ 。
  • 对于设计模式的学习者来说,充分思考这句话其实非常重要, 因为这句往往只对框架/ 工具包的设计才有真正的意义。因为框架和工具包存在的意义,就是为了让其他的程序员予以利用, 进行功能的扩展,而这种功能的扩展必须以不需要改动框架和工具包中代码为前提
  • 对于应用程序的编写者, 从理论上来说, 所有的应用层级代码至少都是处于可编辑范围内的, 如果不细加考量, 就盲目使用较为复杂的设计模式, 反而会得不偿失, 毕竟灵活性的获得, 也是有代价的。

迭代器模式(Iterator Pattern)

  • 设计意图
    • 提供一种方式来访顺序访问集合对象中的每个元素 , 且不暴露该对象的内部表示。
  • GoF 举例

    • 一个集合对象( 例如 list) 应该提供一种不用暴露其内部结构, 就可以遍历其元素的方式。 另外, 你可能会希望以不同的方式遍历一个 list, 但你却并不希望导致 List 接口膨胀成一个包含各种各样遍历方法的接口 , 即便你可以预估哪些遍历方式是你需要的。
  • 上面的描述比较抽象, 我们可以使用一些更为“通俗”的 Java 代码例子来理解 Iterator 产生的问题背景。

  • 如果没有使用Iterator,遍历一个 ArrayList 的方法是使用索引:
    for(int i=0; i<list.size(); i++) { ... list.get(i) ... }
  • 如果没有使用Iterator,遍历一个链表(LinkedList)必须使用while循环
    while((e=e.next())!=null) { ... e.data() ... }
  • 上面两种写法,都需要知道被遍历对象的内部结构, 这样做导致的直接问题是, 当我们需要将一个 ArrayList 换成 LinkedList , 我们需要重新写遍历这部分的代码。
    • 对于大部分 Java 程序员, 遍历更为常见的一种写法可能是使用如下的方式
    String[] strings = {"A""B""C""D"};
    Collection< String> list = java.util.Arrays.asList(strings);
    for (Object str : list) {
        System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */
    }
  • 需要注意的是, 上述写法只是 Java 的语法糖, 编译器最终会将其翻译成如下的形式
    /* 建立一个Collection */
    String[] strings = {"A""B""C""D"};
    Collection stringList = java.util.Arrays.asList(strings);

    /* 开始遍历 */
    for (Iterator 变量名乙 = list.iterator(); 变量名乙.hasNext();) {
        Object str = 变量名乙.next();
        System.out.println(str); /* 依次输出“A”、“B”、“C”、“D” */
    }
  • 解决方案

    • 迭代器模式提供了解决了上述的需求。 迭代器模式的最核心点是把访问和遍历操作从 list 对象中拿出来, 放到 iterator 对象中。 如下图所示 , Iterator 定义了一个访问 list 元素的接口。 Iterator 对象负责在遍历时, 维护当前的元素是哪一个, 哪些元素已经被遍历过等等信息。

    这里写图片描述

    • 在实例化 ListIterator 之前, 必须先提供一个可供遍历的 List ( Java 中, 我们一定是先实例化 List ,才能调用 list.iterator() 获取iterator 的实例)。
    • 把遍历机制从 List 接口的定义中分离出来可以使我们为不同的遍历策略定义不同的 iterator, 而这些遍历策略不用罗列在 List 接口中。 例如 , 一个 FilteringListIterator 可能会仅仅访问那些访问特定过滤条件的元素。
    • 上述这种设计方式的问题是, iterator 和 list 是耦合在一起的, 因为我们定义的是 ListIterator, 所以用户必须知道被遍历的集合对象是一个 List 才能使用 ListIterator 。此时如果更改集合类, 我们不得不更改遍历部分的代码, 为了避免这种问题, 我们可以将 Iterator 进一步抽象 , 采用如下的类关系结构。
      这里写图片描述
    • 图例说明
      • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
      • 图片中的实心三角箭头且箭头, 代表着单一的引用关系, 但是被引用的对象也有可能被其他对象引用。
      • 图片中的虚线实心三角箭头, 代表着创建或者实例化的关系。
    • 上图中, 我们对于 List 接口 , 还有一个跳跃表 SkipList 的实现方式。 跳跃表 skiplist 是一种概率型的 数据结构, 具有和平衡树相似的特征。 AbstractList 和 Iterator 此时都是抽象的接口, 从而具体的 iterator 的迭代方式就和具体的集合类相互独立了。
    • 接下来的问题就是如何创建 iterator。 因为我们在写代码时, 面向 List 编写, 不用关心其具体的实现类, 我们不能去实例化一个具体的类。 因此, 我们让 list 对象负责创建他们对应的 iterator. (在 java 中, 实现 List 接口时, 需要实现 iterator() 方法, 返回一个 Iterator 对象 ) 。

应用场景

  • 希望在不暴露集合对象的内部结构的前提下, 访问集合元素。
  • 希望支持多种对于集合对象的遍历方式
  • 希望为遍历不同的的集合对象提供一个统一的接口

结构图

这里写图片描述

  • 图例说明
    • 图片中的空心三角箭头,代表着继承(extends)或实现(Implement)关系, 由继承者/实现者 指向 被继承者/被继承者。
    • 图片中的实心三角箭头且箭头, 代表着单一的引用关系, 但是被引用的对象也有可能被其他对象引用。
    • 图片中的虚线实心三角箭头, 代表着创建或者实例化的关系。
    • 图片中的末端有圆圈的虚线是一个对方法体内容用伪代码说明的关系

总结

  • 主流编程中的 Collection 类库, 都默认实现了迭代器模式(Iterator Pattern)。 所以该模式我们经常以用户的角度去接触,但自己直接应用的时候却不一定多。 具体而言, 迭代器直接带来的好处如下:
    • 支持对于集合对象遍历方式的灵活变动。
      • 例如, 对于 Tree 的结构存在 深度优先遍历和广度优先遍历之分, 如果是二叉树 BinaryTree , 其深度优先遍历还可以进一步分为: 前序遍历 (Preorder), 中序遍历(Inorder), 后序遍历(Postorder) , 迭代器模式可以让改变遍历算法时, 无须变动遍历代码的写法, 只需要传入一个不同迭代器即可。
    • 简化了集合对象的接口。
      • Iterator 接口中定义的遍历方法避免了类似的方法出现在集合类的接口中, 从而简化了集合对象的接口。
    • 在一个集合对象上,可以有多个遍历并发。
      • 因为每个迭代器可以维护自己对于集合对象遍历状态, 所以同时可以有多个 iterator 遍历同一个对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值