天天看点

Iterator (迭代器)模式

1.1 Iterator 模式

  Iterator 模式用于在数据集合中按照顺序遍历集合。单词 Iterate 有反复做某件事情的医生,汉语称为“迭代器”。

1.2 示例程序

  作用:将书放置到书架(BookShelf)上,并将书的名字按顺序显示出来。

Iterator (迭代器)模式

Iterator 示例程序类图

|| Aggregate 接口

  Aggregate 接口是索要遍历的集合的接口。实现了该接口的类将成为一个可以保存多个元素的集合,像数组一样。Aggregate 有“使聚集”、“集合”的意思。

/**
*
* describe 生成一个用于遍历集合的迭代器
* @author xmc
* @date 2019/3/19 15:11
*/
public interface Aggregate<T> {
   Iterator<T> iterator();
}
           

  在接口中声明的方法只有一个 iterator 方法,该方法会生成一个用于遍历集合的迭代器。

|| Iterator 接口

  Iterator 接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。下面定义了最简单的 Iterator 接口。

/**
*
* describe 最简单的 Iterator 接口
*/
public interface Iterator<T> {

   /**
    *
    * describe 判断是否存在下一个元素
    * @return boolean
    */
   boolean hasNext();

   /**
    *
    * describe 返回下一个元素的方法
    */
   T next();
}
           

  声明两个方法,判断是否存在下一个元素的 hasNext 方法,和获取下一个元素的 next 方法。

  haxNext 方法返回值是 boolean 类型,当集合中存在下一个元素时,返回 true,反正不存在,返回 false,即已经遍历至集合末尾。主要用于循环终止条件。

  next 方法返回的是一个泛型元素,同时还隐含着将迭代器移动至下一个元素的处理。因为 Iterator 接口只知道方法名,具体的还是需要查看 Iterator 实现类中的处理。

|| Book 类

  Book 类是表示书的类,做的事情只有一件-通过 getName 方法获取书的名字。

/**
*
* describe 书 对象
* @author xmc
*/
public class Book {
   private String name;

   public Book(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }
}
           
|| BookShelf 类

  BookShelf 类是表示书架的类。需要将该类作为集合进行处理,因此实现了 Aggregate 接口。且还实现了 Aggregate 接口的 iterator 方法。

/**
*
* describe 书架类
* @author xmc
*/
public class BookShelf implements Aggregate<Book>{
   private Book[] books;
   private int last = 0;

   public BookShelf(int size) {
      this.books = new Book[size];
   }

   public Book getBookAt(int index) {
      return books[index];
   }

   // 书架上添加书
   public void appendBook(Book book) {
      books[last] = book;
      last++;
   }

   public int getLength() {
      return last;
   }

   // 返回迭代器
   @Override
   public Iterator<Book> iterator() {
      return new BookShelfIterator(this);
   }

   public static void main(String[] args) {
      BookShelf bookShelf = new BookShelf(4);
      bookShelf.appendBook(new Book("Around the World in 80 Days"));
      bookShelf.appendBook(new Book("Bible"));
      bookShelf.appendBook(new Book("Cinderella"));
      bookShelf.appendBook(new Book("Daddy-Long-Legs"));
      // 获取迭代器
      Iterator<Book> iterator = bookShelf.iterator();
      // 如果存在下一个,则会一直循环
      while (iterator.hasNext()) {
         System.out.println(iterator.next().getName());
      }
   }
}
           

  书架中定义了 books 字段,它是 Book 类型的数组。在构造器中初始化数组大小。

  iterator 方法会生成并返回 BookShelfIterator 类的实例作为 BookShelf 类对应的 Iterator。

|| BookShelfIterator 类

  用于遍历书架的 BookShelfIterator 类。

public class BookShelfIterator implements Iterator<Book>{
   private BookShelf bookShelf;
   private int index;

   public BookShelfIterator(BookShelf bookShelf) {
      this.bookShelf = bookShelf;
      this.index = 0;
   }

   /**
    *
    * describe 判断是否存在下一个元素
    * @return boolean
    */
   @Override
   public boolean hasNext() {
      return (index < bookShelf.getLength());
   }

   /**
    *
    * describe 返回下一个元素的方法
    * @return T
    */
   @Override
   public Book next() {
      // 取出当前指向的书,同时令 index 指向下一个
      Book book = bookShelf.getBookAt(index);
      // 让迭代器指向下一本书位置
      index++;
      return book;
   }
}
           

  因为BookShelfIterator 类需要发挥 Iterator 的作用,所以它实现了 Iterator 接口。

  bookShefl 字段表示 BookShelfIterator 所要遍历的书架。index 字段表示迭代器当前所指向的书的下标。

  hasNext 方法将会判断书架中还有没有下一本书,如果有就返回 true,没有返回 false。可以通过比较 index 和书架中书的总数来判断。

  next 方法会返回迭代器当前所指向的书,并让迭代器指向下一本书位置。

|| Main 类

  我们使用 Main 类来制作一个小书架。

public class Main {
   public static void main(String[] args) {
      BookShelf bookShelf = new BookShelf(4);
      bookShelf.appendBook(new Book("Around the World in 80 Days"));
      bookShelf.appendBook(new Book("Bible"));
      bookShelf.appendBook(new Book("Cinderella"));
      bookShelf.appendBook(new Book("Daddy-Long-Legs"));
      // 获取迭代器
      Iterator<Book> iterator = bookShelf.iterator();
      // 如果存在下一个,则会一直循环
      while (iterator.hasNext()) {
         System.out.println(iterator.next().getName());
      }
   }
}
           

  while 部分就是 it.hasNext(),只要书架上有书,循环就不会停止。

Around the World in 80 Days
Bible
Cinderella
Daddy-Long-Legs
           

1.3 Iterator 模式中登场的角色

  • Iterator (迭代器)

      该角色负责定义按顺序逐个遍历元素的接口(API)。在示例中,由 Iterator 接口扮演这个角色,定义了 hasNext 和 next 方法。其中,hasNext 方法用于判断是否存在下一个元素,next 方法则用于获取该元素。

  • ConcreateIterator(具体的迭代器)

      该角色负责实现 Iterator 角色所定义的接口(API)。在示例程序中,由 BookShelfIterator 类扮演在这个角色。该角色中包含了遍历集合所必须的信息。

  • Aggregate (集合)

      该角色负责定义创建 Iterator 角色的接口(API)。这个接口是一个方法,会创建出“按顺序访问保存在我内部元素”的对象。在示例程序中,由 Aggregate 接口扮演这个角色,它定义了 iterator 方法。

  • ConcreteAggregate (具体的集合)

      该角色负责实现 Aggregate 角色所定义的接口。它会创建出具体的 Iterator 角色,即 ConcreteIterator 角色。在示例程序中,由 BookShelf 类扮演这个角色,它实现了 iterator 方法。

    Iterator (迭代器)模式

Iterator 模式的类图

1.4 拓展思路要点

|| 不管实现如何变化,都可以使用 Iterator

  为什么一定要引入 Iterator 这个复杂的设计模式?如果是数组,直接 for 循环进行遍历不就可以了么?为什么要在集合之外引入 Iterator 这个角色呢?

  一个重要的原因,引入 Iterator 后可以将遍历与实现分离出来。

while (iterator.hasNext()) {
     System.out.println(iterator.next().getName());
}
           

  这里只使用了 Iterator 的 hasNext 和 next 方法,并没有调用 BookShelf 的方法。也就是说,这里的 While 循环并不依赖于 BookShelf 的实现。

  如果编写 BookShelf 的开发人员决定放弃用数组来管理书本,而使用 ArrayList 会怎么样?不管 BookShelf 如何变化,只要 BookShelf 的 iterator 方法能正确的返回 Iterator 实例,即使不对 while 循环做修改,代码都可以正常工作。这对 BookShelf 的调用者来说真是太方便了。

  设计模式的作用就是帮助我们编写可复用的类。所谓的“可复用”,就是指将类实现为“组件”,当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。

  这样也就能理解为什么示例程序中 iterator 方法的返回值不是 BookSheflIterator 类型而是 Iterator 类型了。这段程序就是要使用 Iterator 的方法进行编程,而不是 BookShelfIterator 的方法。

|| 难以理解抽象类和接口

  难以理解抽象类和接口的人常使用 ConcreteAggregate 角色和 ConcreteIterator 角色编程,而不使用 Aggregate 接口和 Iterator 接口,总想用具体的类来解决所有问题。

  但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。

  “不要只使用具体的类来编程,要优先使用抽象类和接口来编程”。

|| Aggregate 和 Iterator 的对应

  BookShelfIterator 类知道 BookShelf 类是如何实现的。这样,我们才知道如何来获取下一本数的 getBookAt 方法。也就是说,如果BookShelf 的实现发生了改变,即 getBookAt 方法这个接口(API)发生变化时,我们必须修改 BookShelfIterator 类。

  正如 Aggregate 和 Iterator 这两个接口是对应的一样,ConcreteAggregate 和 ConcreteIterator 这两个类也是对应的。

|| 多种 Iterator

  “将遍历功能置于 Aggregate 角色之外” 是 Iterator 模式的一个特征,可以根据一个 ConcreteAggregate 角色编写多个 ConcreteIterator 角色。

  在示例程序中展示的 Iterator 类只是简答地从前向后遍历集合。还可以有其他的遍历方式。

  • 从后向前遍历
  • 既可以从前向后遍历,也可以从后向前遍历(next 和 previous 方法)
  • 指定下标进行“跳跃式”遍历

继续阅读