软件设计模式修炼 -- 迭代器模式

迭代器模式是一种使用频率非常高的设计模式,迭代器用于对一个聚合对象进行遍历。通过引入迭代器可以将数据的遍历功能从聚合对象中分离出来,聚合对象只负责存储数据,聚合对象只负责存储数据,而遍历数据由迭代器来完成。

模式动机

一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构。此外,针对不同的需要,可能还要以不同方式遍历整个聚合对象,但是我们不希在聚合对象的抽象层接口中充斥着各种不同遍历的操作。怎样遍历一个聚合对象,又不需要了解聚合对象的内部结构,还能提供多种不同的遍历方式,这就是迭代器模式所要解决的问题。

迭代器模式中,提供一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义一个访问该聚合元素的接口,并且可以跟踪当前遍历对象,了解哪些元素已经遍历过而哪些没有。

模式定义

提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为模式。

模式结构

  1. Iterator(抽象迭代器)

    抽象迭代器定义了访问和遍历元素的接口,一般声明以下方法:

    • 用于获取第一个元素的 first()
    • 用于访问下一个元素的 next()
    • 用于判断是否还有下一个元素的 hasNext()
    • 用于获取当前元素的 currentItem()
  2. ConcreteIterator(具体迭代器)

    具体迭代器实现了抽象迭代器接口,完成对聚合对象的遍历,同时在对聚合进行遍历时跟踪其当前位置

  3. Aggregate(抽象聚合类)

    抽象聚合类用于存储对象,并定义创建相应迭代器对象的接口,声明一个 createIterator() 方法用于创建一个迭代器对象

  4. ConcreteAggregate(具体聚合类)

    具体聚合类实现了创建相应迭代器的接口,实现了在聚合类中声明的 createIterator() 方法,该方法返回一个与具体聚合对应的具体迭代器 ConcreteIterator 实例

模式分析

存储数据是聚合对象的最基本职责,其中包含存储数据的类型、存储空间的大小、存储空间的分配,以及存储的方式和顺序。然而,聚合对象除了能存储数据外,还必须提供遍历访问其内部数据的方式,同时这些遍历方式可能会根据不同的情形提供不同的实现。

因此,聚合对象主要有两个职责:一是存储内部数据;二是遍历内部数据。前者是聚合对象的基本功能,后者是可以分离的。根据单一职责原则,对象承担的职责越少,对象的稳定性就越好,我们将遍历聚合对象中数据的行为提取出来,封装到一个迭代器中,通过专门的迭代器来遍历聚合对象的内部数据。迭代器模式是单一职责原则的完美体现。

下面通过一个简单的自定义迭代器来分析迭代器模式的结构

首先定义一个简单的迭代器去接口

public interface MyIterator {
    void first();// 访问第一个元素
    void next();// 访问下一个元素
    boolean isLast();// 判断是否是最后一个元素
    Object currentItem();// 获取当前元素
}

然后需要定义一个聚合接口

public interface MyCollection {
    // 返回一个 MyIterator 迭代器对象
    MyIterator createIterator();
}

定义好抽象层之后,我们需要定义抽象迭代器接口和抽象聚合接口的实现类,一般将具体迭代器类作为具体聚合类的内部类,从而迭代器可以实现直接访问聚合类中的数据

public class NewCollection implements MyCollection {

    private Object[] obj = {"dog", "pig", "cat", "monkey", "pig"};

    public MyIterator createIterator() {
        return new NewIterator();
    }

    private class NewIterator implements MyIterator {

        private int currentIndex = 0;

        public void first() {
            currentIndex = 0;
        }

        public void next() {
            if(currentIndex < obj.length) {
                currentIndex++;
            }
        }

        public boolean isLast() {
            return currentIndex == obj.length;
        }

        public void currentItem() {
            return obj[currentIndex];
        }
    }
}

NewCollection 类实现了 MyCollection 接口,实现了 createIterator() 方法,同时定义了一个数组用于存储数据元素,还定义了一个实现了 MyIterator 接口的内部类,索引变量 currentIndex 用于保存所操作的数组元素的下标值。客户端代码如下:

public class Client {

    public static void process(MyCollection collection) {
        MyIterator i = collection.createIterator();
        while(!i.isLast()) {
            System.out.println(i.currentItem().toString());
            i.next();
        }
    }

    public static void main(String args[]) {
        MyCollection collection = new NewCollection();
        process(collection);
    }
}

除了使用内部类实现之外,也可以使用常规的方式来实现迭代器

public class ConcreteIterator implements Iterator {

    private ConcreteAggregate objects;

    public ConcreteIterator(ConcreteAggregate objects) {
        this.objects = objects;
    }

    public void first() {
        ...
    }

    public void next() {
        ...
    }

    public boolean isLast() {
        ...
    }

    public void currentItem() {
        ...
    }
}

public class ConcreteAggregate implements Aggregate {
    ...
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }
}

迭代器模式中应用了工厂方法模式,聚合类充当工厂类,而迭代器充当产品类

模式优缺点

迭代器模式优点:

  1. 支持以不同的方式遍历一个聚合对象。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,也可以自己定义迭代器的子类以支持新的遍历方式。
  2. 迭代器简化了聚合类。原有聚合对象不再需要自行提供遍历数据等操作方法。
  3. 在同一个聚合上可以有多个遍历。由于每个迭代器都保持自己的遍历状态,因此可以对一个聚合对象进行多个遍历操作。
  4. 增加新的聚合类和迭代器类都很方便,无须修改原代码,满足开闭原则。

迭代器模式缺点:

  1. 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,在一定程度上增加了系统的复杂性

模式适用环境

在以下情况可以使用迭代器模式:

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

Java 迭代器

Java 中的集合框架 Collections,其基本接口层次结构如图

Collection 是所有集合类的根接口,它的主要方法如下:

boolean add(Object c);
boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);
Iterator iterator();

Collection 的 iterator() 方法返回一个 java.util.Iterator 类型的对象,而其子接口 java.util.List 的 listIterator() 方法返回一个 java.util.ListIterator 类型的对象,ListIterator 是 Iterator 的子类,它们构成了 Java 语言对迭代器模式的支持。

在 JDK 中,Iterator 接口具有如下三个基本方法:

  1. Object next():通过反复调用 next() 方法可以逐个访问聚合中的元素
  2. boolean hasNext():用于判断聚合对象在是否还存在下一个元素,为了不抛出异常,必须在调用 next() 之前先调用 hasNext()。如果迭代对象仍然拥有可供访问的元素,那么 hasNext() 返回 true
  3. void remove():删除上一次调用 next() 时返回的元素

Java 迭代器可以理解为它工作时在聚合对象的各个元素之间,每调用一次 next() 方法,迭代器便越过下个元素,并且返回它刚越过的那个元素的地址引用。

在第一个 next() 方法被调用时,迭代器由“元素1”与“元素2”之间移动至“元素2”与“元素3”之间,跨越了“元素2”,因此 next() 方法将返回对“元素2”的引用;在第二个 next() 方法被调用时,迭代器由“元素2”与“元素3”之间移至“元素3”与“元素4”之间,next() 方法将返回对“元素3”的引用,此时调用 remove() 方法,则可将”元素3“删除。

需要注意的是,next() 方法与 remove() 方法的调用是相互关联的。如果调用 remove() 之前没有先对 next() 进行调用,那么将抛出异常,因为没有任何可供删除的元素

(0)

相关推荐

  • 大话设计模式笔记(十七)の迭代器模式

    迭代器模式 定义 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示. 什么时候用? 当你需要访问一个聚集对象,而且不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式. ...

  • 无语!这道迭代器笔试题,居然难倒很多人

    有位小朋友最近正在为年后换工作做准备,但是遇到一个问题,觉得很不可思议的一道笔试题.然后我把这道题发到技术群里,发现很多人居然不知道,很多都是连蒙带猜的说.感觉很有必要写一篇文章来说道说道. 涨薪必备 ...

  • 软件设计模式修炼 -- 访问者模式

    访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素具有不同的类型,且不同的访问者可以对其进行不同的访问操作 模式动机 对于系统中某些对象,它们存储在同 ...

  • 软件设计模式修炼 -- 解释器模式

    解释器是一种不常使用的设计模式,它用于描述如何构成一个简单的语言解释器,主要应用于使用面向对象语言开发的编译器和解释器设计.当我们需要开发一个新的语言时,可以考虑使用解释器模式 模式动机 如果在系统中 ...

  • PHP设计模式之迭代器模式

    PHP设计模式之迭代器模式 一说到这个模式,就不得不提循环语句.在<大话设计模式>中,作者说道这个模式现在的学习意义更大于实际意义,这是为什么呢?当然就是被foreach这货给整得.任何语 ...

  • [PHP小课堂]PHP设计模式之迭代器模式

    [PHP小课堂]PHP设计模式之迭代器模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 软件设计模式修炼 -- 观察者模式

    观察者模式是一种经常使用的设计模式,在软件系统中对象并不是孤立存在的,一个对象行为的改变可能会导致其他与之存在依赖关系的对象行为发生改变,观察者模式用于描述对象之间的依赖关系. 模式动机 很多情况下, ...

  • 软件设计模式-中介者模式

    使用中介者模式来说明联合国的作用,要求绘制相应的类图并分析每个类的作用(注:可以将联合国定义为抽象中介者类,联合国下属机构如WIO,WHO,WTO等作为具体者类,国家作为抽象同事类,而将中国,美国等国 ...

  • PHP设计模式—迭代器模式

    定义: 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示. 结构: Iterator:迭代器接口,用于定义得到开始对象.得到下一个对象.判断是否到 ...

  • 设计模式之迭代器与组合模式(三)

    现在我们已经能愉快地看着一页一页罗列出来的菜单进行点菜了.现在又有的小伙伴希望能够加上一份餐后甜点的"子菜单".怎么办呢?我们不仅仅要支持多个菜单,甚至还要支持菜单中的菜单. 如果 ...