设计模式01——Iterator模式

2019-04-14 20:20发布

class="markdown_views prism-atom-one-light">

定义

迭代器(Iterator)模式,从其英文单词可以看出,有反复做某件事的意思。迭代器模式常用于数据集合中,对数据集合中的数据按照顺序进行遍历。它能提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。

问题引入

假设我们有一个书架,假设书架可以按照顺序放置无数本书,现在有一个需求,那就是遍历书架里面的所有的书籍,将书籍的名称打印出来。
这里写图片描述

常规的解决办法

一般情况下,我们在用代码来解决该问题的时候,都是想到使用for循环,对书架中的每一本书进行循环遍历,然后输出名称。这是一种比较常规的做法,考虑的问题就是为了遍历书架中的书籍,并使用变量i来记录当前遍历到哪一本书了,以及将指针指向下一本书,伪代码如下: List books = new ArrayList<>(); books.add(new Book("深入理解Java虚拟机")); books.add(new Book("深入分析Java Web技术内幕")); books.add(new Book("Java编程思想")); books.add(new Book("Linux就该这么学")); for (int i = 0; i < books.size(); i++) { System.out.println(books.get(i).getName()); } 这里的for语句中的i++的作用是将i的值在每一次循环之后就自增1,使得在遍历的过程中就可以访问集合中的下一本书籍,下下一本书籍,再下下一本书籍,直到遍历完所有的书籍。如果我们考虑将i的作用进行抽象化,使其通用化,那么就可以形成迭代器模式。

从List中获取启发

说到迭代器设计模式,大家肯定会想到java.util包中的集合,是的,Java中集合类中运用到了迭代器设计模式,那么,我们在学习迭代器设计模式的时候,完全可以通过去学习集合的源码来学习迭代器设计模式,下面,我将从ArrayList出发,探究一下在ArrayList中如何运用的迭代器设计模式。
作为集合类的顶级接口,Collection接口继承了Iterable接口,Iterable接口的子接口或者实现类都具备迭代和遍历的功能,那么List接口继承自Collection,自然也是具备基本的迭代功能的,那么我们从List出发,来探究迭代器模式的运用。
List接口中有一个重写自Collectioniterator()方法,它的返回值是一个Iterator接口的实现类对象。
这里写图片描述
那么我们接着看ArrayList类,它实现了List接口,也实现了List接口中的iterator()方法。
这里写图片描述
那么,针对实例化ArrayList的时候传入的具体的泛型,可以生成其对应的迭代器,也就是说,上图中new语句后面的Itr()创造出来的迭代器对象肯定是针对传入的泛型的迭代器对象。我们继续阅读代码,发现ItrArrayList的私有内部类,它还实现了Iterator接口,也实现了基本的hasNext()方法和next()方法。
这里写图片描述
因为ItrArrayList的内部类,那么Itr可以操作ArrayList的成员变量,而ArrayList作为集合,它内部的元素是存储在变量名为elementDataObject数组中的,整个遍历的过程就是针对这个数组进行遍历的。
分析完源码,我们也许还是有些迷糊,那么我们需要借助UML类图来描述这些接口或者类之间的关系了。
这里写图片描述
从上图可以看出,List接口中有创建Iterator接口的实现类对象的抽象方法,而ArrayList实现了List接口,而Itr类实现了Iterator接口,且其拥有ArrayList的元素数组,可以针对该数组进行一系列的操作,比如遍历。类图可以很清晰表表现出类与类之间的关系,不太熟悉的可以去学习一下UML类图。

手动实现迭代器设计模式

List中获取到迭代器设计模式实现的灵感,那么我们需要自己手动实现自己的迭代器模式,来解决文章开始引入的遍历书架的问题。首先,我们根据List中采用的迭代器设计模式,我们需要一个集合接口Aggregate(有“使聚集”、“聚合”的意思),该集合接口中有创建迭代器接口Iterator实现类对象的抽象方法iterator,这个迭代器接口有hasNextnext方法,用来判断集合是否还有元素以及获取集合元素,我们还需要具体的集合实现类,也就是具体的书架,用来承载书籍,该实现类实现了Aggregate集合的iterator方法,最后,我们还需要实现迭代器接口的具体类,它持有集合的具体的实现类,还实现了hasNextnext方法。我们将类图设计如下所示:
这里写图片描述
根据类图,我们可以很轻松地设计出基本的迭代器设计模式及的代码,主要代码如下所示:
  • 集合接口Aggregate
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 是需要遍历的集合的接口 * * @author jiangpingping * @date 2018/8/27 下午9:31 */ public interface Aggregate<E> { /** * 返回当前集合的迭代器 * * @return 当前集合的迭代器 */ Iterator iterator(); } 该接口遵循类图的设计,接口中包含创建Iterator接口实现类对象的方法。
  • 迭代器接口Iterator
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 迭代器接口 * * @author jiangpingping * @date 2018/8/27 下午9:32 */ public interface Iterator<E> { /** * 检测是否有下一个元素 * * @return 如果有,返回true,否则返回false */ boolean hasNext(); /** * 返回当前元素,并将指针指向下一个元素 * * @return 当前元素 */ E next(); } 该迭代器接口有两个方法,分别是判断被遍历的集合是否还有元素以及获取集合中元素并将指针指向下一个元素。
  • 具体的集合类BookShelf
package cn.itlemon.design.pattern.chapter01.iterator.example; import java.util.ArrayList; /** * 书架,其实就是承载书籍的集合或数组 * * @author jiangpingping * @date 2018/8/27 下午9:42 */ public class BookShelf<E> implements Aggregate { private ArrayList books; private int last = 0; public BookShelf(int initSize) { books = new ArrayList<>(initSize); } public E getBookAt(int index) { return books.get(index); } public void appendBook(E e) { books.add(e); last++; } public int getLength() { return last; } @Override public Iterator iterator() { return new BookShelfIterator<>(this); } } getBookAt方法可以获取指定索引位置的书籍,appendBook方法是向书架中最后一个位置添加书籍的方法,getLength方法可以得到书籍的数量,iterator方法可以获取当前书架的迭代器实现类对象。
  • 迭代器实现类BookShelfIterator
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * {@link BookShelf} 的迭代器 * * @author jiangpingping * @date 2018/8/27 下午9:49 */ public class BookShelfIterator<E> implements Iterator<E> { private BookShelf bookShelf; private int index; public BookShelfIterator(BookShelf bookShelf) { this.bookShelf = bookShelf; index = 0; } @Override public boolean hasNext() { return index < bookShelf.getLength(); } @Override public E next() { E e = bookShelf.getBookAt(index); index++; return e; } } hasNext方法可以判断遍历的集合中是否还有未遍历的元素,next方法直接获取元素,并将在指针指向下一个元素,所以在使用List的迭代器的时候,要特别留意next方法,在每次循环的过程中,next方法只能使用一次,多次使用的话,单次遍历会引起逻辑错误。
  • 书籍类Book
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 书籍的实体类 * * @author jiangpingping * @date 2018/8/27 下午9:39 */ public class Book { private String name; public Book(String name) { this.name = name; } public String getName() { return name; } }
  • 测试类Main
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 测试Iterator模式的主类 * * @author jiangpingping * @date 2018/8/27 下午10:01 */ public class Main { public static void main(String[] args) { BookShelf bookShelf = new BookShelf<>(4); bookShelf.appendBook(new Book("深入理解Java虚拟机")); bookShelf.appendBook(new Book("深入分析Java Web技术内幕")); bookShelf.appendBook(new Book("Java编程思想")); bookShelf.appendBook(new Book("Linux就该这么学")); Iterator iterator = bookShelf.iterator(); while (iterator.hasNext()) { Book book = iterator.next(); System.out.println(book.getName()); } } } 看到上面的测试主类,和使用ArrayList的迭代器一模一样,也就是说,我们手动实现了迭代器设计模式,其实,在设计过程中,所有的泛型都是使用E来进行代替的,所以这些类不仅仅适用Book的遍历,支持其他类的遍历,如果在将类的名称修改一下,改成更加通用的,那么这个迭代器就将适合更多的集合遍历,遍历的元素可以是其他的类对象。

浅析迭代器模式中的重要角 {MOD}

任何模式都是前人积累下来的经验,大多数模式中都有固定的角 {MOD},使用模式的时候,可以根据固定的角 {MOD}来编写代码,就可以轻松地利用上设计模式,使自己的代码更具有“可复用性”。接下来将浅析迭代器设计模式中的重要角 {MOD}。
  • Iterator(迭代器接口)
    该角 {MOD}负责定义按顺序逐个遍历元素的接口(API),在本次示例中,由Iterator接口扮演了这个角 {MOD},它定义了两个方法,hasNextnext方法,分别是判断被遍历的集合或者数组是否还有下一个元素以及直接获取元素,并将指针指向下一个元素。
  • ConcreteIterator(具体的迭代器)
    该角 {MOD}实现了Iterator所定义的接口(API),在本次示例中,由BookShelfIterator类来扮演了这个角 {MOD},它持有需要遍历的数组或者集合的具体信息,也就是说需要被遍历的集合存在于这个迭代器中。
  • Aggregate(集合)
    该角 {MOD}负责定义创建Iterator角 {MOD}的接口(API),这个接口会创建出一个Iterator的实现类对象,这个实现类对象可以对实现Aggregate集合接口类对象进行遍历。本次示例中,由Aggregate接口来扮演这个角 {MOD}。
  • ConcreteAggregate(具体的集合)
    该角 {MOD}负责实现Aggregate所定义的接口(API),它会创建出具体的Iterator角 {MOD},也就是ConcreteIterator角 {MOD}。在本次示例中,由BookShelf扮演了这个角 {MOD},它实现类iterator方法。

迭代器设计模式UML类图

这里写图片描述

为什么要使用迭代器设计模式

为什么在遍历的时候要额外引入迭代器这种复杂的模式呢? 也许你有这样的疑问,引入迭代器设计模式的一个重要理由是:将实现和遍历进行了分离,也就是说遍历的过程完全不依赖与你所选择的集合是如何实现的,在示例中,使用的ArrayList来承载数据的,如果开发者换了其他的容器来承载数据,那么只需要修改集合的实现方式即可,完全不需要去修改遍历的过程代码,这就提高了代码的“可复用性”和“可靠性”。