博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式 | 迭代器模式及典型应用
阅读量:7050 次
发布时间:2019-06-28

本文共 10818 字,大约阅读时间需要 36 分钟。

本文的主要内容:

  • 介绍迭代器模式
  • 源码分析迭代器模式的典型应用
    • Java集合中的迭代器模式
    • Mybatis中的迭代器模式

更多内容可访问我的个人博客:

关注【小旋锋】微信公众号,及时接收博文推送

迭代器模式

迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。

角色

Iterator(抽象迭代器):它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法,例如:用于获取第一个元素的first()方法,用于访问下一个元素的next()方法,用于判断是否还有下一个元素的hasNext()方法,用于获取当前元素的currentItem()方法等,在具体迭代器中将实现这些方法。

ConcreteIterator(具体迭代器):它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数。

Aggregate(抽象聚合类):它用于存储和管理元素对象,声明一个createIterator()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。

ConcreteAggregate(具体聚合类):它实现了在抽象聚合类中声明的createIterator()方法,该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator实例。

在迭代器模式中,提供了一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。迭代器的引入,将使得对一个复杂聚合对象的操作变得简单。

在迭代器模式中应用了工厂方法模式,抽象迭代器对应于抽象产品角色,具体迭代器对应于具体产品角色,抽象聚合类对应于抽象工厂角色,具体聚合类对应于具体工厂角色。

示例

我们来实现一个学生报数的示例

定义一个学生类,有一个报数方法 count()

@Getter@Setter@ToStringpublic class Student {    private String name;    private Integer number;    public Student(String name, Integer number) {        this.name = name;        this.number = number;    }    public void count() {        System.out.println(String.format("我是 %d 号 %s", this.number, this.name));    }}复制代码

定义班级接口和班级类

public interface StudentAggregate {    void addStudent(Student student);    void removeStudent(Student student);    StudentIterator getStudentIterator();}public class StudentAggregateImpl implements StudentAggregate {    private List
list; // 学生列表 public StudentAggregateImpl() { this.list = new ArrayList
(); } @Override public void addStudent(Student student) { this.list.add(student); } @Override public void removeStudent(Student student) { this.list.remove(student); } @Override public StudentIterator getStudentIterator() { return new StudentIteratorImpl(list); }}复制代码

定义迭代器接口并实现迭代器

public interface StudentIterator {    boolean hashNext();    Student next();}public class StudentIteratorImpl implements StudentIterator{    private List
list; private int position = 0; private Student currentStudent; public StudentIteratorImpl(List
list) { this.list = list; } @Override public boolean hashNext() { return position < list.size(); } @Override public Student next() { currentStudent = list.get(position); position ++; return currentStudent; }}复制代码

测试,进行报数

public class Test {    public static void main(String[] args) {        StudentAggregate classOne = new StudentAggregateImpl();        classOne.addStudent(new Student("张三", 1));        classOne.addStudent(new Student("李四", 2));        classOne.addStudent(new Student("王五", 3));        classOne.addStudent(new Student("赵六", 4));        // 遍历,报数        StudentIterator iterator = classOne.getStudentIterator();        while (iterator.hashNext()){            Student student = iterator.next();            student.count();        }    }}复制代码

输出

我是 1 号 张三我是 2 号 李四我是 3 号 王五我是 4 号 赵六复制代码

迭代器模式类图如下

迭代器模式总结

迭代器模式的主要优点如下:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 "开闭原则" 的要求。

迭代器模式的主要缺点如下:

  • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。

适用场景:

  • 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
  • 需要为一个聚合对象提供多种遍历方式。
  • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。

源码分析迭代器模式的典型应用

Java集合中的迭代器模式

java.util.ArrayList

public class ArrayList
extends AbstractList
implements List
, RandomAccess, Cloneable, java.io.Serializable { transient Object[] elementData; // non-private to simplify nested class access private int size; public E get(int index) { rangeCheck(index); return elementData(index); } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public ListIterator
listIterator() { return new ListItr(0); } public ListIterator
listIterator(int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException("Index: "+index); return new ListItr(index); } public Iterator
iterator() { return new Itr(); } private class Itr implements Iterator
{ 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 != size; } public E next() { //... } public E next() { //... } public void remove() { //... } //... } private class ListItr extends Itr implements ListIterator
{ public boolean hasPrevious() { return cursor != 0; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } public E previous() { //... } public void set(E e) { //... } public void add(E e) { //... } //...}复制代码

ArrayList 源码中看到了有两个迭代器 ItrListItr,分别实现 IteratorListIterator 接口;

第一个当然很容易看明白,它跟我们示例的迭代器的区别是这里是一个内部类,可以直接使用 ArrayList 的数据列表;第二个迭代器是第一次见到, ListIteratorIterator 有什么区别呢?

先看 ListIterator 源码

public interface ListIterator
extends Iterator
{ boolean hasNext(); E next(); boolean hasPrevious(); // 返回该迭代器关联的集合是否还有上一个元素 E previous(); // 返回该迭代器的上一个元素 int nextIndex(); // 返回列表中ListIterator所需位置后面元素的索引 int previousIndex(); // 返回列表中ListIterator所需位置前面元素的索引 void remove(); void set(E var1); // 从列表中将next()或previous()返回的最后一个元素更改为指定元素e void add(E var1); }复制代码

接着是 Iterator 的源码

public interface Iterator
{ boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } // 备注:JAVA8允许接口方法定义默认实现 default void forEachRemaining(Consumer
action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }}复制代码

通过源码我们看出:ListIterator 是一个功能更加强大的迭代器,它继承于 Iterator 接口,只能用于各种List类型的访问。可以通过调用 listIterator() 方法产生一个指向List开始处的 ListIterator, 还可以调用 listIterator(n) 方法创建一个一开始就指向列表索引为n的元素处的 ListIterator

IteratorListIterator 主要区别概括如下:

  • ListIteratoradd() 方法,可以向List中添加对象,而 Iterator 不能
  • ListIteratorIterator 都有 hasNext()next() 方法,可以实现顺序向后遍历,但是 ListIteratorhasPrevious()previous() 方法,可以实现逆向(顺序向前)遍历。Iterator 就不可以。
  • ListIterator 可以定位当前的索引位置,nextIndex()previousIndex() 可以实现。Iterator 没有此功能。
  • 都可实现删除对象,但是 ListIterator 可以实现对象的修改,set() 方法可以实现。Iierator 仅能遍历,不能修改。

敲一个 Iterator 的 Demo 探究一下

public class Test3 {    public static void main(String[] args) {        List
list = new ArrayList
(); list.add("张三"); list.add("李四"); list.add("王五"); list.add("赵六"); Iterator
iterator = list.iterator(); String first = iterator.next(); System.out.println("first: " + first); System.out.println("-----------next-------------"); while (iterator.hasNext()){ System.out.println(iterator.next()); } iterator.remove(); System.out.println("-----------list-------------"); for (String name: list){ System.out.println(name); } }}复制代码

输出结果

first: 张三-----------next-------------李四王五赵六-----------list-------------张三李四王五复制代码

可以看到 Iterator.remove() 会删除原来的 List 对象的数据

再敲一个 ListIterator 的 Demo 探究一下

public class Test2 {    public static void main(String[] args) {        List
list = new ArrayList
(); list.add("张三"); list.add("李四"); list.add("王五"); list.add("赵六"); ListIterator
listIterator = list.listIterator(); String first = listIterator.next(); listIterator.set("小明"); System.out.println("first: " + first); System.out.println("-----------next-------------"); listIterator.add("大明"); while (listIterator.hasNext()){ System.out.println(listIterator.nextIndex() + ": " + listIterator.next()); } listIterator.remove(); System.out.println("------------previous------------"); while (listIterator.hasPrevious()){ System.out.println(listIterator.previousIndex() + ": " + listIterator.previous()); } System.out.println("-----------list-------------"); for (String name: list){ System.out.println(name); } }}复制代码

结果如下

first: 张三-----------next-------------2: 李四3: 王五4: 赵六------------previous------------3: 王五2: 李四1: 大明0: 小明-----------list-------------小明大明李四王五复制代码

可以看出 ListIteratoraddsetremove 方法会直接改变原来的 List 对象,而且可以通过 previous 反向遍历

Mybatis中的迭代器模式

当查询数据库返回大量的数据项时可以使用游标 Cursor,利用其中的迭代器可以懒加载数据,避免因为一次性加载所有数据导致内存奔溃,Mybatis 为 Cursor 接口提供了一个默认实现类 DefaultCursor,代码如下

public interface Cursor
extends Closeable, Iterable
{ boolean isOpen(); boolean isConsumed(); int getCurrentIndex();}public class DefaultCursor
implements Cursor
{ private final DefaultResultSetHandler resultSetHandler; private final ResultMap resultMap; private final ResultSetWrapper rsw; private final RowBounds rowBounds; private final ObjectWrapperResultHandler
objectWrapperResultHandler = new ObjectWrapperResultHandler
(); // 游标迭代器 private final CursorIterator cursorIterator = new CursorIterator(); protected T fetchNextUsingRowBound() { T result = fetchNextObjectFromDatabase(); while (result != null && indexWithRowBound < rowBounds.getOffset()) { result = fetchNextObjectFromDatabase(); } return result; } @Override public Iterator
iterator() { if (iteratorRetrieved) { throw new IllegalStateException("Cannot open more than one iterator on a Cursor"); } iteratorRetrieved = true; return cursorIterator; } private class CursorIterator implements Iterator
{ T object; int iteratorIndex = -1; @Override public boolean hasNext() { if (object == null) { object = fetchNextUsingRowBound(); } return object != null; } @Override public T next() { 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 中的迭代器几乎一样

参考:

刘伟:设计模式Java版
慕课网java设计模式精讲 Debug 方式+内存分析

推荐阅读

转载地址:http://kepol.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
WPF“动画序列”框架的初步研究与实现(附源码)
查看>>
校招求职面试连载(二)
查看>>
网络学习(三十一)操作系统无人值守自动安装之Windows XP
查看>>
handler 机制
查看>>
解决mysql无法导入本地文件的问题
查看>>
HBase 系统架构
查看>>
RichFace标签学习笔记
查看>>
iOS中block介绍(四)揭开神秘面纱(下)
查看>>
更改yum源为阿里云的yum源
查看>>
解决exchang服务器连接不可用问题
查看>>
Tomcat启动权限
查看>>
一步一步學習partitions之hash partitions
查看>>
POJ 1061 青蛙的约会 扩展欧几里得
查看>>
我的友情链接
查看>>
随笔-ftp文件上传,删除
查看>>
Ansible问题汇总
查看>>
linux上部署hadoop集群 基础篇
查看>>
java中堆(heap)和堆栈(stack)
查看>>
H3C 5500/5820 端口聚合LACP
查看>>