前言介绍


接下里介绍的是Java 的设计模式之一:迭代器模式

我们还是以一个问题进行展开,引入迭代器模式

编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院, 一个学院有多个系。如图

图片.png

我们之前用组合模式解决过这个问题,但是我们现在要以遍历的角度去思考

怎么遍历他们?

一、传统方式解决问题

图片.png

比如说目前

计算机学院采用的是数组的方式存储、信息学院采用集合存储

那么我们怎么去遍历他们?

解决方案:=> 迭代器模式

二、什么是迭代器模式

迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式

如果我们的集合元素是用不同的方式实现的,有数组,还有集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。

迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构

迭代器原理类图分析

图片.png

Iterator :迭代器接口由系统提供,含义 hasNext, next, remove

ConcreteIterator :具体的迭代器类,管理迭代

Aggregate :一个统一的聚合接口将客户端和具体聚合解耦

ConcreteAggreage : 具体的聚合持有对象集合, 并提供一个方法,返回一个迭代器, 该迭代器可以正确遍历集合

Client :客户端, 通过 Iterator 和 Aggregate 依赖子类

三、使用迭代器模式解决问题

图片.png

根据我们的思路,因为学院是包含系的,所以我们需要先创建系这个类

//系
class Department {

    private String name;//名称
    private String desc;//描述
    public Department(String name, String desc) {
        super();
        this.name = name;
        this.desc = desc;
    }

    public String getName() {return name;}

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

    public String getDesc() {return desc;}

    public void setDesc(String desc) {this.desc = desc;}
}

假设我们计算机学院采用的是数组的方式存储相关的计算机系

那么我们根据思路创建计算机学院实现迭代器接口的实现类

class ComputerCollegeIterator implements Iterator {

    //这里Department是以数组的方式存放
    Department[] departments;

    //遍历的位置
    int position = 0; 

    public ComputerCollegeIterator(Department[] departments) {
        this.departments = departments;
    }

    //判断是否还有下一个元素
    @Override
    public boolean hasNext() {
        if (position >= departments.length || departments[position] == null) {
            return false;
        } else {
            return true;
        }
    }
    
    @Override
    public Object next() {
        Department department = departments[position];
        position += 1;
        return department;
    }

    //删除的方法,默认空实现
    @Override
    public void remove() {}
}

假设我们信息学院采用的是集合的方式存储相关的信息系

那么我们根据思路创建信息学院实现迭代器接口的实现类

class InfoColleageIterator implements Iterator {

    //  信息工程学院是以 List 方式存放系
    List<Department> departmentList;

    //索引
    int index = -1;

    public InfoColleageIterator(List<Department> departmentList) {
        this.departmentList = departmentList;
    }

    //判断 list 中还有没有下一个元素
    @Override
    public boolean hasNext() {
        if (index >= departmentList.size() - 1) {
            return false;
        } else {
            index += 1;
            return true;
        }
    }

    @Override
    public Object next() {
        return departmentList.get(index);
    }

    // 空 实 现 remove
    @Override
    public void remove() {}
}

接下来我们根据思路创建返回迭代器的接口

interface College {
    //学院的名称
    public String getName();

    //增加系的方法
    public void addDepartment(String name, String desc);

    //返回一个迭代器,遍历
    public Iterator    createIterator();
}

我们根据思路首先创建于计算机学院对应的返回迭代器实现类

class ComputerCollege implements College {

    //这里Department是以数组的方式存放
    Department[] departments;

    // 保存当前数组的对象个数
    int numOfDepartment = 0;

    @Override
    public String getName() {
        return "计算机学院";
    }

    @Override
    public void addDepartment(String name, String desc) {
        //根据传入的信息创建系
        Department department = new Department(name, desc);
        departments[numOfDepartment] = department;
        numOfDepartment += 1;
    }

    @Override
    public Iterator createIterator() {
        return new ComputerCollegeIterator(departments);
    }
}

我们再根据思路创建于信息学院对应的返回迭代器实现类

class InfoCollege implements College {

    //集合的方式存储
    List<Department> departmentList;

    @Override
    public String getName() {
        return "信息工程学院";
    }
    @Override
    public void addDepartment(String name, String desc) {
        Department department = new Department(name, desc);
        departmentList.add(department);
    }
    @Override
    public Iterator createIterator() {
        return new InfoColleageIterator(departmentList);
    }
}

计算机学院使用数组的方式存储、信息学院使用集合的方式存储

这时我们创建一个输出类,通过迭代器的方式输出所有学院

class OutPutImpl {

    //学院集合
    List<College> collegeList;

    public OutPutImpl(List<College> collegeList) {
        this.collegeList = collegeList;
    }

    //遍历所有学院,然后调用 printDepartment  输出各个学院的系
    public void printCollege() {
        //从 collegeList 取出所有学院, Java 中的 List 已经实现Iterator
        Iterator<College> iterator = collegeList.iterator();
        while (iterator.hasNext()) {
            //取出一个学院
            College college = iterator.next();
            System.out.println("=== " + college.getName() + "=====");
            printDepartment(college.createIterator()); //得到对应迭代器
        }
    }

    //输出 学院输出 系
    public void printDepartment(Iterator iterator) {
        while (iterator.hasNext()) {
            Department d = (Department) iterator.next();
            System.out.println(d.getName());
        }
    }
}

接下来我们使用demo 一起体会看看,是怎么将两组不同存储方式遍历的

public static void main(String[] args) {

    //创建存储迭代类
    List<College> collegeList = new ArrayList<College>();

    //创建返回对应计算机学院迭代器
    ComputerCollege computerCollege = new ComputerCollege();
    //创建存储计算机学院的里的系
    computerCollege.departments = new Department[5];
    //将计算机学院里的系,存储到迭代器里
    computerCollege.addDepartment("Java 专业", " Java 专业  ");
    computerCollege.addDepartment("PHP 专业", " PHP 专业  ");
    computerCollege.addDepartment("大数据专业", "  大数据专业 ");

    //创建返回对应信息学院对应迭代器
    InfoCollege infoCollege = new InfoCollege();
    //创建存储计算机学院的里的系
    infoCollege.departmentList = new ArrayList<Department>();
    //将信息学院里的系,存储到迭代器里
    infoCollege.addDepartment("信息安全专业", " 信息安全专业 ");
    infoCollege.addDepartment("网络安全专业", " 网络安全专业 ");
    infoCollege.addDepartment("服务器安全专业", "  服务器安全专业 ");

    //将两个学院的迭代器添加进集合中
    collegeList.add(computerCollege);
    collegeList.add(infoCollege);

    //将集合交给帮忙输出类进行输出
    OutPutImpl outPutImpl = new OutPutImpl(collegeList);
    outPutImpl.printCollege();
}

运行结果如下:
=== 计算机学院=====
Java 专业
PHP 专业
大数据专业
=== 信息工程学院=====
信息安全专业
网络安全专业
服务器安全专业

四、迭代器在源码中的使用

我们先使用一个示例,来看看ArrayList的使用与输出

public static void main(String[] args) {

    List<String> list = new ArrayList<>();
    list.add("Jack");

    Iterator<String> integer = list.iterator();
    while(integer.hasNext()){
        System.out.println(integer.next());
    }
}

运行结果如下:
Jack

诶,为什么ArrayList可以使用Iterator迭代器呢?

我们一起进ArrayList的源码里去看看,发现它实现了List集合的接口

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //....省略关键性的代码
}

那么List集合这个接口做了什么事情?我们也进去看看

public interface List<E> extends Collection<E> {
    
    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();
    
    //省略其他关键性代码.....
}

诶,我们发现List集合里有一个iterator方法

那么ArrayList 有没有对着这个方法实现呢?我们一起进去找找看看

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Iterator<E> iterator() {
        return new Itr();
    }
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

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

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

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

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
    //省略其他关键性代码....
}

我们这样就发现了,一下就思路就清醒了,请看类图分析

图片.png

Itr是ArrayList的一个内部类,本身使用ArrayList里的elementData

因此与原先迭代模式不一样,它是直接使用

五、迭代器模式的注意事项和细节


Ø 优点

提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。

隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。

提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。

在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。

而如果遍历方式改变的话,只影响到了迭代器。

当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

Ø 缺点

每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

参考资料


尚硅谷:设计模式(韩顺平老师):迭代器模式

Refactoring.Guru:《深入设计模式》


28640
116 声望25 粉丝

心有多大,舞台就有多大