前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式01——Iterator模式

设计模式01——Iterator模式

作者头像
itlemon
发布2020-04-03 16:32:18
5080
发布2020-04-03 16:32:18
举报
文章被收录于专栏:深入理解Java深入理解Java
定义

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

问题引入

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

这里写图片描述
这里写图片描述
常规的解决办法

一般情况下,我们在用代码来解决该问题的时候,都是想到使用for循环,对书架中的每一本书进行循环遍历,然后输出名称。这是一种比较常规的做法,考虑的问题就是为了遍历书架中的书籍,并使用变量i来记录当前遍历到哪一本书了,以及将指针指向下一本书,伪代码如下:

代码语言:javascript
复制
List<Book> 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
代码语言:javascript
复制
package cn.itlemon.design.pattern.chapter01.iterator.example;

/**
 * 是需要遍历的集合的接口
 *
 * @author jiangpingping
 * @date 2018/8/27 下午9:31
 */
public interface Aggregate<E> {

    /**
     * 返回当前集合的迭代器
     *
     * @return 当前集合的迭代器
     */
    Iterator<E> iterator();

}

该接口遵循类图的设计,接口中包含创建Iterator接口实现类对象的方法。

  • 迭代器接口Iterator
代码语言:javascript
复制
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
代码语言:javascript
复制
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<E> 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<E> iterator() {
        return new BookShelfIterator<>(this);
    }
}

getBookAt方法可以获取指定索引位置的书籍,appendBook方法是向书架中最后一个位置添加书籍的方法,getLength方法可以得到书籍的数量,iterator方法可以获取当前书架的迭代器实现类对象。

  • 迭代器实现类BookShelfIterator
代码语言:javascript
复制
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<E> bookShelf;

    private int index;

    public BookShelfIterator(BookShelf<E> 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
代码语言:javascript
复制
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
代码语言:javascript
复制
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<Book> 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<Book> iterator = bookShelf.iterator();
        while (iterator.hasNext()) {
            Book book = iterator.next();
            System.out.println(book.getName());
        }
    }
}

看到上面的测试主类,和使用ArrayList的迭代器一模一样,也就是说,我们手动实现了迭代器设计模式,其实,在设计过程中,所有的泛型都是使用E来进行代替的,所以这些类不仅仅适用Book的遍历,支持其他类的遍历,如果在将类的名称修改一下,改成更加通用的,那么这个迭代器就将适合更多的集合遍历,遍历的元素可以是其他的类对象。

浅析迭代器模式中的重要角色

任何模式都是前人积累下来的经验,大多数模式中都有固定的角色,使用模式的时候,可以根据固定的角色来编写代码,就可以轻松地利用上设计模式,使自己的代码更具有“可复用性”。接下来将浅析迭代器设计模式中的重要角色。

  • Iterator(迭代器接口) 该角色负责定义按顺序逐个遍历元素的接口(API),在本次示例中,由Iterator接口扮演了这个角色,它定义了两个方法,hasNextnext方法,分别是判断被遍历的集合或者数组是否还有下一个元素以及直接获取元素,并将指针指向下一个元素。
  • ConcreteIterator(具体的迭代器) 该角色实现了Iterator所定义的接口(API),在本次示例中,由BookShelfIterator类来扮演了这个角色,它持有需要遍历的数组或者集合的具体信息,也就是说需要被遍历的集合存在于这个迭代器中。
  • Aggregate(集合) 该角色负责定义创建Iterator角色的接口(API),这个接口会创建出一个Iterator的实现类对象,这个实现类对象可以对实现Aggregate集合接口类对象进行遍历。本次示例中,由Aggregate接口来扮演这个角色。
  • ConcreteAggregate(具体的集合) 该角色负责实现Aggregate所定义的接口(API),它会创建出具体的Iterator角色,也就是ConcreteIterator角色。在本次示例中,由BookShelf扮演了这个角色,它实现类iterator方法。
迭代器设计模式UML类图
这里写图片描述
这里写图片描述
为什么要使用迭代器设计模式

为什么在遍历的时候要额外引入迭代器这种复杂的模式呢?

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-09-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 问题引入
  • 常规的解决办法
  • 从List中获取启发
  • 手动实现迭代器设计模式
  • 浅析迭代器模式中的重要角色
  • 迭代器设计模式UML类图
  • 为什么要使用迭代器设计模式
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档