学习设计模式——迭代器模式

·  阅读 197
学习设计模式——迭代器模式

概述

相信大家平时开发过程中没少用过遍历集合、数组这些语法。 一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。

实际上,foreach 循环只是一个语法糖而已,底层是基于迭代器来实现的。foreach 循环代码的底层实现,就是第三种遍历方式(迭代器遍历)。这两种遍历方式可以看作同一种遍历方式,也就是迭代器遍历方式。

迭代器模式:(Iterator Design Pattern)也叫做游标模式(Cursor Design Pattern)。提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。

何时使用:

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  2. 需要为聚合对象提供多种遍历方式。
  3. 为遍历不同的聚合结构提供一个统一的接口。

UML 类图:

image.png 角色组成:

  1. 抽象容器(Aggregate):一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
  2. 具体容器(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
  3. 抽象迭代器(Iterator):定义按顺序逐个遍历元素的接口,定义遍历元素所需要的方法取得下一个元素的方法next()和判断是否遍历结束的方法hasNext()。

通用代码

Iterator.java

public interface Iterator {

    boolean hasNext();
    Object next();

}

复制代码

ConcreteIterator.java

public class ConcreteIterator implements Iterator {

    private List list;
    private int cursor = 0;

    public ConcreteIterator(List list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        if (cursor == list.size()) {
            return false;
        }
        return true;
    }

    @Override
    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = this.list.get(cursor++);
        }
        return obj;
    }
}

复制代码

Aggregate.java

public interface Aggregate {

    void add(Object obj);
    void remove(Object obj);
    Iterator iterator();

}
复制代码

ConcreteAggregate.java

public class ConcreteAggregate implements Aggregate {
    private List list = new ArrayList();
    @Override
    public void add(Object obj) {
        list.add(obj);
    }

    @Override
    public void remove(Object obj) {
        list.remove(obj);
    }

    @Override
    public Iterator iterator() {
        return new ConcreteIterator(list);
    }
}
复制代码

测试:

public class Test {
    public static void main(String[] args) {

        Aggregate aggregate = new ConcreteAggregate();
        aggregate.add("小胖");
        aggregate.add("小黑");
        aggregate.add("小六");
        Iterator iterator = aggregate.iterator();
        while (iterator.hasNext()) {
            String str = (String) iterator.next();
            System.out.println(str);
        }

    }
}

复制代码

结果:

小胖
小黑
小六

迭代器模式的优势

  • 简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。

  • 可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。

  • 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。

ArrayList 相关源码

接下来我们看一下 ArrayList 遍历相关的源码使用的迭代器模式:

首先是接口 Iterator.java

public interface Iterator<E> {
    
    boolean hasNext();
    E next();
    // 省略其他代码
}
复制代码

然后是类 Itr ,对应着 ConcreteIterator 角色,不过这个类是 ArrayList 的一个内部类。

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;

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

    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];
    }

   

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
    // 省略其他代码
}
复制代码

接下来是类 List ,对应着 Aggregate 角色。

public interface List<E> extends Collection<E> {
    Iterator<E> iterator();
    // 省略其他代码
}
复制代码

最后是类 ArrayList , 对应着 ConcreteAggregate 角色。

public class ArrayList<E> extends AbstractList<E> implements List<E>
{
    public Iterator<E> iterator() {
        return new Itr();
    }
    
    private class Itr implements Iterator<E> {
        // 此处代码省略
    }
    // 省略其他代码
    
}
复制代码

总结

有了 for 循环遍历,我们为什么还需要迭代器遍历呐?

对于类似数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。

看这样一个例子:

public class Test {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person("小黑", 25));
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Person p = (Person)it.next();
            System.out.println(p.getName());
        }
    }

    static class Person {
        String name;
        int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
    }
}
复制代码
        while (it.hasNext()) {
            Person p = (Person)it.next();
            System.out.println(p.getName());
        }
复制代码

这里使用了 Iterator 的 hasNex 方法和 next 方法,并没有调用 ArrayList 的方法。也就是说,这里的 while 循环并不依赖于 ArrayList 的实现。

假如 ArrayList 的开发人员决定切换新的遍历算法的时候,只需要修改迭代器类的代码即可,只要 ArrayList 的 iterator 方法能正确的返回 Iterator 的实例,即使不对上面的 while 循环做任何修改,代码都可以正常工作。

分类:
后端
收藏成功!
已添加到「」, 点击更改