设计模式之迭代器模式

Posted 活跃的咸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之迭代器模式相关的知识,希望对你有一定的参考价值。

迭代器模式的引入

如果要问java中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式,先来看一段代码吧:

public static void print(Collection coll){
	Iterator it = coll.iterator();
	while(it.hasNext()){
		String str = (String)it.next();
		System.out.println(str);
	}
}

这个方法的作用是循环打印一个字符串集合,里面就用到了迭代器模式,java语言已经完整地实现了迭代器模式,Iterator翻译成汉语就是迭代器的意思。提到迭代器,首先它是与集合相关的,集合也叫聚集、容器等,我们可以将集合看成是一个可以包容对象的容器,例如List,Set,Map,甚至数组都可以叫做集合,而迭代器的作用就是把容器中的对象一个一个地遍历出来。

迭代器模式介绍

基本介绍

  1. 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
  2. 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,
    或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历
    方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  3. 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,
    不需要知道集合对象的底层表示,即:不暴露其内部的结构。

原理类图
在这里插入图片描述
抽象容器(Aggregate):它用于存储和管理元素对象,声明一个createIterator()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。

具体的聚集类(ConcerateAggregate):实现createIterator()方法返回具体的一个迭代器

具体迭代器(ConcreteIterator):它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数。

抽象迭代器(Iterator):定义遍历元素所需要的方法,一般来说会有这么几个方法:取得第一个元素的方法first(),取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove()。

迭代器模式示例

下面我们用一个专业管理的例子来实现迭代器模式的演示:

public class Major {
    private String name;//专业名称
    private String desc;//专业描述

    public Major(String name, String desc) {
        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;
    }

    @Override
    public String toString() {
        return "Project{" +
                "name='" + name + '\\'' +
                ", desc='" + desc + '\\'' +
                '}';
    }
}

抽象迭代器(Iterator)

public interface MajorIterator {
    boolean hasNext();
    Major next();
}

具体迭代器(ConcreteIterator):

public class MajorIteratorImpl implements MajorIterator{
    private List<Major> list;
    private int index=0;

    public MajorIteratorImpl(List<Major> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return index<list.size();
    }

    @Override
    public Major next() {
       Major major=list.get(index);
        index++;
        return major;
    }
}

抽象容器(Aggregate):

public interface majorAggregate {
    void addMajor(Major major);
    void removeMajor(Major major);
    MajorIterator getMajorIterator();
}

具体的聚集类(ConcerateAggregate)

public class majorAggregateImpl implements majorAggregate{
    private List<Major> list;

    public majorAggregateImpl() {
    }

    public majorAggregateImpl(List<Major> list) {
        this.list = list;
    }

    @Override
    public void addMajor(Major major) {
        this.list.add(major);
    }

    @Override
    public void removeMajor(Major major) {
           this.list.remove(major);
    }

    @Override
    public MajorIterator getMajorIterator() {
        return new MajorIteratorImpl(list);
    }
}

clent

public class Test {
    public static void main(String[] args) {
        majorAggregate majorAggregate=new majorAggregateImpl(new ArrayList<>());
        majorAggregate.addMajor(new Major("软件工程","软件工程专业"));
        majorAggregate.addMajor(new Major("计算机科学与技术","计算机科学与技术专业"));
        majorAggregate.addMajor(new Major("网络工程","网络工程"));

        MajorIterator majorIterator=majorAggregate.getMajorIterator();
        while (majorIterator.hasNext()){
            System.out.println(majorIterator.next());
        }
    }
}

Project{name='软件工程', desc='软件工程专业'}
Project{name='计算机科学与技术', desc='计算机科学与技术专业'}
Project{name='网络工程', desc='网络工程'}

迭代器模式jdk源码ArrayList中分析

我们平时使用ArrayList

public class Test {
    public static void main(String[] args) {
       List<String> list=new ArrayList<>();
       list.add("abc");
       list.add("def");
       list.add("xyz");
       list.add("jkl");
       Iterator<String> iterator = list.iterator();
       while (iterator.hasNext()){
           System.out.println(iterator.next());
       }
    }
}

类图如下:
在这里插入图片描述
角色说明:

  • 内部类Itr 充当具体实现迭代器Iterator 的类, 作为ArrayList 内部类
  • List 就是充当了聚合接口,含有一个iterator() 方法,返回一个迭代器对象
  • ArrayList 是实现聚合接口List 的子类,实现了iterator()
  • Iterator 接口系统提供

下面我们来看源码:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
    
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
    
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
    
    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;
        
        public boolean hasNext() {
            return cursor != size;
        }
        
        public E next() {
            //...
        }
        
        public E next() {
            //...
        }
        
        public void remove() {
            //...
        }
        //...
    }  
    
    private class ListItr extends Itr implements ListIterator<E> {
        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }
        
        public E previous() {
            //...
        }
        
        public void set(E e) {
            //...
        }
        
        public void add(E e) {
            //...
        }
    //...
}

从 ArrayList 源码中看到了有两个迭代器 Itr 和 ListItr,分别实现 Iterator 和 ListIterator 接口;
先看 ListIterator 源码

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();  // 返回该迭代器关联的集合是否还有上一个元素
    E previous();           // 返回该迭代器的上一个元素
    int nextIndex();        // 返回列表中ListIterator所需位置后面元素的索引
    int previousIndex();    // 返回列表中ListIterator所需位置前面元素的索引
    void remove();
    void set(E var1);       // 从列表中将next()或previous()返回的最后一个元素更改为指定元素e
    void add(E var1);   
}

接着是 Iterator 的源码

public interface Iterator<E> {
    boolean hasNext();
    E next();
    
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    // 备注:JAVA8允许接口方法定义默认实现
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

通过源码我们看出:ListIterator 是一个功能更加强大的迭代器,它继承于 Iterator 接口,只能用于各种List类型的访问。可以通过调用 listIterator() 方法产生一个指向List开始处的 ListIterator, 还可以调用 listIterator(n) 方法创建一个一开始就指向列表索引为n的元素处的 ListIterator。

Iterator 和 ListIterator 主要区别概括如下:

ListIterator 有 add() 方法,可以向List中添加对象,而 Iterator 不能
ListIterator 和 Iterator 都有 hasNext() 和 next() 方法,可以实现顺序向后遍历,但是 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向(顺序向前)遍历。Iterator 就不可以。
ListIterator 可以定位当前的索引位置,nextIndex() 和 previousIndex() 可以实现。Iterator 没有此功能。
都可实现删除对象,但是 ListIterator 可以实现对象的修改,set() 方法可以实现。Iierator 仅能遍历,不能修改。

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

优点:

  1. 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以
    遍历对象了。
  2. 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚
    合的具体组成。
  3. 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任
    原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集
    合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变
    的话,只影响到了迭代器。
  4. 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

缺点:

  1. 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  2. 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。

适用场景:

  1. 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
  2. 需要为一个聚合对象提供多种遍历方式。
  3. 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。

以上是关于设计模式之迭代器模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之迭代器模式

设计模式之行为型迭代器模式

设计模式之迭代器模式

设计模式之迭代器模式

JavaScript---设计模式之迭代器模式

设计模式之迭代器模式 Iterator