迭代器模式——Iterator

关于迭代器(Iterator)

 迭代器这个名词想来我们已经不陌生了,在集合中我们就学习过迭代器,并掌握了如何使用迭代器来遍历集合中的元素。

例如:
List<String> list = new ArrayList<>();
list.add("anc");
list.add("cvl");
list.add("uio");
list.add("ope");

Iterator iterator = list.iterator();
while(iterator.hasNext()){
      System.out.print(list.next());
}
输出:anc cvl uio ope

 实际上,迭代器就是反复同一件事的一个循环,就如同for(int i = 0; i < 100; i++)中的i++,只要符合条件就加1,直到遍历完每一个元素后就退出,而迭代器模式(Iterator模式)的思想也就是来自于此。迭代器模式是一种没落的模式,除非产品性质的需要,否则不会单独去写一个迭代器模式(Java中的集合就对迭代器模式进行优秀的封装,基本上不用认为扩展,都能满足我们的需求)。我们可以看看迭代器的定义:迭代器提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的细节。通俗点说,迭代器的诞生就是为遍历容器(容器就是能持有对象的集合,如Collection,Set)中的元素服务的。

迭代器模式——你在不在?我要揍你一顿

举一个例子,详细讲讲什么是迭代器模式

 在一个风和日丽的早晨,阳光明媚,春暖花开,打开古老的藏书阁,我要找一本心仪的古书陶冶一下情操,可是让我很郁闷的是,由于时间太久没人整理,很多书都凌乱的放在书架上,所以我费了老大的劲儿才把书籍找到。我就在想,要是有一个这样的物件就好了,它能在我想要从一堆杂乱无章的事物中寻找某一件东西时,它能按整齐的把它排列(排列顺序自己选择)好,这样我找的时候不就容易多了,我冥思苦想很久,找了很多上古典籍查阅(《设计模式之禅》,《图解设计模式》)中找到了答案,那就是上古秘书——迭代器模式。

 现有一个书架BookShelf,有一堆书Book,我想把书放到书架上,并按照书的名字按顺序显示出来。看一下示例图

程序的类图如下

类和接口

逐步剖析(请直接看代码)

Aggregate接口:表示集合的接口,相当于Java集合中Collection,List等集合接口

/**
 * 表示集合的接口
 */
public interface Aggregate {

    /**
       * 该方法用于生成一个遍历结合的迭代器
       *
       * 在遍历集合中的元素时吗,调用iterator方法生成一个实现了Iterator接口的类的实例
       * @return
      */
    public abstract Iterator iterator();

}

Iterator接口:表示用于遍历集合的接口

/**
 * 用于遍历集合的接口:遍历集合中的元素,其作用相当于循环语句中的循环变量
 */
public interface Iterator {

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

     /**
       * 返回集合中的一个元素
       * @return
      */
    public abstract Object next();

}

BookShelf类:实现了Aggregate接口,表示一个书籍的集合,可以看成Java集合框架中的实现类

/**
 * 表示书架类,可以看成是一个书的集合,所以需要实现Aggregate接口
 */
public class BookShelf<T> implements Aggregate {

private Book[] books;// 可见性设置为private,避免不小心篡改
private int last;//标识最后一本书的索引

public BookShelf(int maxsize) {
     this.books = new Book[maxsize];// 生成BookShelf实例的时候指定了books的大小
}

     //根据索引得到书籍
public Book getBookAt(int index) {
     return books[index];
}

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

    //返回书籍的数量
public int getLength() {
    return last;
}

    @Override
public Iterator iterator() {
     return new BookShelfIterator<T>(this);
}

}

BookShelfIterator类:Iterator的实现类

/**
 * 遍历书籍的迭代器实现(Iterator)类
 * @param <T>
 */
public class BookShelfIterator<T> implements Iterator {
private BookShelf<T> bookShelf;//表示BookShelfIterator要遍历的书架
private int index;//表示迭代器当前指向的书的索引

 //初始化
public BookShelfIterator(BookShelf<T> bookShelf) {
      this.bookShelf = bookShelf;
      this.index = 0;
}

 @Override
public boolean hasNext() {
<!-- if (index < bookShelf.getLength()) {//如果当前索引小于书架书籍的数量,表名下一本书还存在,继续遍历
         return true;
} else {
         return false;
} -->

 return index != bookShelf.getLength;
}

/**
     * next()方法有两个作用:
     *          >返回当前索引对应的书籍实例
     *          >并将index指向下一个元素 >>>> 这和for循环中的i++很像
*/
@Override
public Object next() {
     Book book = bookShelf.getBookAt(index);//将对应索引的书一一返回
     index++;
     return book;
 }
}

Book类:书籍类(实体类)

public class Book {

private String name;

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

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Book [name=" + name + "]";
}

}

下面是Main方法

public class Main {

  public static void main(String[] args) {

    BookShelf<Book> bookShelf = new BookShelf<>(4);

    bookShelf.appendBook(new Book("雪国"));
    bookShelf.appendBook(new Book("山中古音"));
    bookShelf.appendBook(new Book("天堂"));
    bookShelf.appendBook(new Book("一个人的路"));

    Iterator iterator = bookShelf.iterator();

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

}

运行结果:
Book [name=雪国]
Book [name=山中古音]
Book [name=天堂]
Book [name=一个人的路]

集合框架中的迭代器模式(源码解析)

 上面是我们自己写的迭代器模式,用我们自己的想法实现了一个简单的迭代器,怎么样,你对迭代器了解有多少了呢,如果你还是不清楚,那我们再来看看Java集合框架是如何运用迭代器的。我将主要以源码的方式解析,你会发现其实和我上面写的差不多。

我就使用List接口进行解析,List定义了如下方法
Iterator<E> iterator();

Iterator接口:
public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    //该方法与函数式接口有关(lambda表达式)
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

以List的一个实现类ArrayList来看看如何实现的

public Iterator<E> iterator() {
       return new Itr();
   }

//Itr是AbstractList的一个成员内部类(其实ArrayList也有一个Itr的成员内部类
  只不过这是优化版本)
private class Itr implements Iterator<E> {
      int cursor;       //表示下一个要访问的元素的索引
      int lastRet = -1; //表示上一个元素的索引
      int expectedModCount = modCount;//表示对ArrayList修改次数的期望值,它的初始值为modCount(modCount是AbstractList类中的一个成员变量)

      public boolean hasNext() {
        return cursor != size();
      }

     public E next() {
         checkForComodification();
         try {
             int i = cursor;//下一个元素的索引
             E next = get(i);//通过该索引得到实例
             lastRet = i;//将该索引赋值给表示上一个元素索引的变量
             cursor = i + 1;//指向下一个元素
             return next;//返回当前实例
         } catch (IndexOutOfBoundsException e) {
             checkForComodification();
             throw new NoSuchElementException();
         }
     }

     public void remove() {
         if (lastRet < 0)
             throw new IllegalStateException();
         checkForComodification();

         try {
             AbstractList.this.remove(lastRet);
             if (lastRet < cursor)
                 cursor--;
             lastRet = -1;
             expectedModCount = modCount;
         } catch (IndexOutOfBoundsException e) {
             throw new ConcurrentModificationException();
         }
     }

     final void checkForComodification() {
         if (modCount != expectedModCount)
             throw new ConcurrentModificationException();
     }
  }

深入研究一下

 OK,上面就是迭代器在集合框架中的使用,现在我们或许就理解了为什么在学习集合的迭代器器时说,我们是去获取一个迭代器,而并非是我们new一个迭代器对象,下面我们来看看迭代器模式的类图,并分析一下我们为什么要使用迭代器

  • Iterator(迭代器):该角色定义了顺序逐个遍历元素的接口
  • ConcreteIterator(具体的迭代器):实现Iterator接口
  • Aggregate(集合):定义创建Iterator角色的接口
  • ConcreteAggregate(具体的集合):该角色负责实现Aggregate所定义的接口

 看了上面介绍的迭代器模式,我们或许头有点晕,既然同是迭代,为什么我们不去使用更为普通的迭代方式,如for循环呢,非要用这种复杂的设计模式干什么,其实回到前面迭代器模式的定义:迭代器模式提供一种顺序访问一个聚合对象中各个元素的方法,而又不暴露该对象的内部实现。

举个例子:我是车站的售票员,对于我来说,我只是负责售票的,其他的事情我一概不管,对于我来说,只有一个原则,那就是只有买了票才能乘车,不管你是中国人外国人,不管你是小偷还是杀人犯,只要你没买票就不能乘车,我不用管乘车对象是什么,我只管你有没有买票。

 如同上面举的那个例子,这里只是用了Iterator的hasNext和next方法,并没有调用BookShelf的方法,也就是说,while循环并不依赖于BookShelf的实现。不管BookShelf如何变化,只要BookShelf的iterator方法能返回正确的Iterator实例,即使不对while循环做任何修改,都可以正常工作。设计模式的作用就是帮助我们编写可复用的类。而可复用的含义就是将类视为“组件”,当一个组件方法发生变化时,不需要对其他组件进行修改或者只需要进行很小的修改就可以了,大大提高了代码的重用率。

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

结语

迭代器模式是一种古老的设计模式,也是一种非常常见的设计模式,它已经完美的镶嵌于聚合关系中,如Java的集合框架,几乎每一个集合类都对迭代器模式进行了封装。正是由于它的普遍性,所以很多大佬都提出将迭代器模式从23中设计模式中删除。不管迭代器模式在未来是否还能存在于设计模式家族中,我们只需要知道,应用之广泛则证明其价值,所以值得我们去学习研究它的思想精髓。

推荐书籍与网站:

猜你喜欢

转载自blog.csdn.net/king123456man/article/details/81626274