设计模式--迭代器模式

什么是迭代器模式?

让用户通过特定的接口访问容器的数据,不需要了解容器内部的数据结构。

假设我们不使用迭代器模式

package Iterator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class IterateWithoutIterator {

    private List list;

    public void setContainer(List list) {
        this.list = list;
    }

    // 访问并且处理容器数据的方法
    public void printElemtents() {
        // 访问list容器内的数据
        if (list == null) throw new NullPointerException();
        for (int i = 0; i < list.size(); i ++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) throws NullPointerException {
        IterateWithoutIterator it = new IterateWithoutIterator();
        
        List list = new ArrayList<Integer>();
        for (int i = 0; i < 10; i ++)
            list.add(i);
        
        it.setContainer(list);
        it.printElemtents();
    }

}

可以看到当前这个方法和要访问的容器之间是高度耦合的,因为我需要知道这个容器实现的数据访问方法才可以访问容器内部数据。

当然如果当前方法和容器接口进行耦合也可以让这个方法的通用性更强,比如list接口实现的容器肯定是实现了get方法来访问容器数据,但是如果有个容器,我们称之为容器C没有实现当前方法绑定的接口,那你就得去方法里面把容器数据访问代码改成符合这个容器C的了。

容器的实现机制各有不同,如何让这个方法对所有容器都通用?或者说,如何通过只修改容器源而不修改方法内的遍历逻辑

这时候,我们就需要引入iterator模式


容器实现Iterable接口,致力于提供符合Iterator实现的数据格式。需要遍历容器数据的方法则是使用Iterator提供的数据遍历方法进行数据访问,这样我们处理容器数据的逻辑就和容器本身的实现解耦了,因为我们只需要使用Iterator接口就行了,只要使用的容器提供Iterator对象,我们完全不用关心容器怎么实现,底层数据如何访问之类的问题。而且更换容器的时候也不需要修改数据处理逻辑。

package Iterator;

public interface Iterator<E> {

    boolean hasNext();

    E next();

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

}

可以看到,Iterator中最重要的两个方法就是next方法和hasNext方法,构成了遍历整个容器数据的两个方法。remove方法如果没有实现的话默认是会抛出一个不支持该操作的异常。

package Iterator;

public interface Iterable<E> {

    Iterator<E> createIterator();

}

Iterable接口十分简单,提供一个可以输出Iteator对象的方法。

package Iterator;

public class MyContainer<E> implements Iterable<E>{

    Object[] elements;

    public MyContainer() {
        elements = new Byte[10];
        for (int i =0; i < 10; i ++)
            elements[i] = (byte) i;
    }

    private class Itr<E> implements Iterator<E> {
        private int position = -1;
        private Object[] data = elements;
        @Override
        public boolean hasNext() {
            return ++ position < data.length;
        }

        @Override
        public E next() {
            return (E) data[position];
        }
    }

    @Override
    public Iterator<E> createIterator() {
        return new Itr();
    }

}

容器实现Iterable接口。

package Iterator;

public class IterateWithIterator {
    private Iterator elements;

    public void setContainer(Iterator newElements) {
        this.elements = newElements;
    }

    // 访问并且处理容器数据的方法
    public void printElemtents() {
        if (elements == null) throw new NullPointerException();
        // 访问list容器内的数据
        while (elements.hasNext()) {
            System.out.println(elements.next());
        }

    }

    public static void main(String[] args) {
        IterateWithIterator it = new IterateWithIterator();
        it.setContainer(new MyContainer<Byte>().createIterator());
        it.printElemtents();
    }
}

拥有容器数据处理方法的类持有一个实现Iterator接口的对象,并使用Iterator接口方法进行数据访问。

为什么不让容器直接继承Iterator接口,Iterable接口会不会有一种多此一举的感觉?

个人理解是这样的,是为了让数据处理逻辑和容器内部数据管理进行分离,提供容器内数据安全性。假设容器实现了Iterator接口,那么我们所有通过Iterator接口进行的数据访问修改操作都会直接影响容器内的数据,因为我们访问的数据和容器维护的数据是同一份数据。其实这样做是很不安全的,因为你也不知道用户访问这个数据会做什么操作,那不如让Iterator接口访问数据副本来的安全。这样大家各管各的,互不影响。

2018/06/06更新

迭代器模式可以控制外部访问容器内部数据的顺序。


猜你喜欢

转载自blog.csdn.net/zhangzhetaojj/article/details/80550309