设计模式学习笔记(12)迭代器

本文实例代码:https://github.com/JamesZBL/java_design_patterns

迭代器(Iterator)模式又称游标模式,是集合类型对外提供统一的顺序访问元素而隐藏内部的实现细节的一种方式,是一种行为模式。

迭代器在 Java 的集合类中非常常见,我们使用迭代器来遍历集合中的每一个元素。迭代器在 ArrayList 的使用通常是这样的:

String[] array = new String[]{"百度", "阿里", "腾讯"};
List<String> list = Arrays.asList(array);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

输出:

百度
阿里
腾讯

我们使用 Iterator 接口的一个实例来访问这个 List 实例,Iterator 接口非常小巧,定义了两个方法:

public interface Iterator<E> {
    // 判断是否有后继元素
    boolean hasNext();
    // 获取下一个元素的引用,执行此方法后,“游标”自动向后移动
    E next();
}

Java 中的“容器”类的之间的关系:

Collection 接口继承自 Iterable:

public interface Iterable<T> {
    Iterator<T> iterator();
}

所以可以理解为所有的集合都是可遍历的,因为集合就是一系列元素的“容器”。而 Collection 接口中的 iterator() 方法返回一个 Iterator 接口的引用,所以可以对所有的 Collection 的子类调用 iterator() 方法来获取这个容器实例的迭代器。对于不同的容器的实现,其内部数据结构是不同的,所以具体的迭代方式自然也不尽相同,但是它们都通过这个统一的接口方法来获取迭代器,迭代的实现被巧妙的隐藏了。

实例

现实生活中有一个常用的场景和迭代器的工作方式非常相似,那就是在图书馆中寻找自己想要的一本书,我们通常的做法是找到这个类目的书架,然后按一定的顺序一本一本的找,这个过程可以大致视为迭代器遍历书架这个容器。

我们把这个书架上的书分为三个大类:IT、小说和卡通,用一个枚举类来表示:

ItemType.java

public enum ItemType {
  IT, FICTION, CARTOON
}

和 JDK 中提供给我们的 Iterator 接口类似,我们也定义一个 Iterator 接口:

ItemIterator.java

public interface ItemIterator {

  boolean hasNext();

  Item next();
}

定义一个书架类 BookShelf,它持有一个 List 类型的引用,表示书架上所有图书的集合:

BookShelf.java

public class BookShelf {

  private List<Item> items;

  public BookShelf() {
    items = new ArrayList<>();
    items.add(new Item(ItemType.FICTION, "西游记"));
    items.add(new Item(ItemType.FICTION, "水浒传"));
    items.add(new Item(ItemType.FICTION, "三国演义"));
    items.add(new Item(ItemType.FICTION, "红楼梦"));
    items.add(new Item(ItemType.CARTOON, "阿衰"));
    items.add(new Item(ItemType.CARTOON, "七龙珠"));
    items.add(new Item(ItemType.CARTOON, "火影忍者"));
    items.add(new Item(ItemType.IT, "设计模式-可复用面向对象软件的基础"));
    items.add(new Item(ItemType.IT, "重构-改善既有代码的设计"));
    items.add(new Item(ItemType.IT, "Effective Java"));
    items.add(new Item(ItemType.IT, "Java编程思想"));
  }

  public List<Item> getItemList() {
    List<Item> list = new ArrayList<>();
    list.addAll(items);
    return list;
  }
}

现在来定义专门为 BookShelf 服务的迭代器,它实现 ItemIterator 接口:

BookShelfIterator.java

public class BookShelfIterator implements ItemIterator {

  private ItemType type;
  private BookShelf shelf;
  private int idx;

  public BookShelfIterator(ItemType type, BookShelf shelf) {
    this.type = type;
    this.shelf = shelf;
    this.idx = -1;
  }

  @Override
  public boolean hasNext() {
    return -1 != getNexIdx();
  }

  @Override
  public Item next() {
    idx = getNexIdx();
    if (-1 != idx) {
      return shelf.getItemList().get(idx);
    }
    return null;
  }

  private int getNexIdx() {
    List<Item> list = shelf.getItemList();
    int tempIdx = idx;
    boolean found = false;
    while (!found) {
      tempIdx++;
      if (tempIdx >= list.size()) {
        tempIdx = -1;
        break;
      }
      if (list.get(tempIdx).getType().equals(type)) {
        break;
      }
    }
    return tempIdx;
  }
}

这里我们间接的使用了 List 接口提供的方法 get() 来获取第 n 个元素,其内部实现同样被隐藏了,这里只是演示遍历的过程,因此不必考虑 List 的内部数据结构。

显然,这和 Collection 中的结构是不同的,BookShelfIterator 持有 BookShelf 的引用,而不是由 BookShelf 来生成适用于它自身的迭代器,虽然看起来比较别扭,不过还是符合实际情况的,迭代器要拿到书架这个对象才能遍历它。如果大范围的使用,还是应当仿照 Collection 中的设计思路,避免出现过多的类,给系统的复杂度造成负担。

现在拿一个装满书的书架来试一下这个迭代器的效果:

App.java

public class Application {

  private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);

  public static void main(String[] args) {
    // 查找小说
    ItemIterator iterator = new BookShelfIterator(ItemType.FICTION, new BookShelf());
    LOGGER.info("正在查找小说类图书");
    while (iterator.hasNext()) {
      Item nextItem = iterator.next();
      LOGGER.info("找到了符合条件的图书,书名为:{}", nextItem.toString());
    }

    // 查找IT
    ItemIterator iterator2 = new BookShelfIterator(ItemType.IT, new BookShelf());
    LOGGER.info("正在查找IT类图书");
    while (iterator2.hasNext()) {
      Item nextItem = iterator2.next();
      LOGGER.info("找到了符合条件的图书,书名为:{}", nextItem.toString());
    }

    // 查找漫画
    ItemIterator iterator3 = new BookShelfIterator(ItemType.CARTOON, new BookShelf());
    LOGGER.info("正在查找漫画类图书");
    while (iterator3.hasNext()) {
      Item nextItem = iterator3.next();
      LOGGER.info("找到了符合条件的图书,书名为:{}", nextItem.toString());
    }
  }
}

总结

迭代器模式提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。迭代器模式关乎遍历集合的解决思路是把游标在元素之间移动的职责转交给迭代器,而不是集合对象自己。

迭代器模式的优点有:
1、它支持以不同的方式遍历一个集合合对象
2、迭代器模式简化了集合类
3、在同一个聚合上可以有多个遍历
4、使用迭代器模式,新建聚合类和迭代器,无须修改原有代码

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.