图解设计模式——Iterator模式(一个一个遍历)

简介: 使用 Java 或者 C++ 语言显示数组 arr 中的元素时,我们可以使用以下这样的 for 循环语句来

图解设计模式——Iterator模式(一个一个遍历)

Iterator 模式—— 一个一个遍历

使用 Java 或者 C++ 语言显示数组 arr 中的元素时,我们可以使用以下这样的 for 循  环语句来遍历数组:

示例程序

Aggregate 接口

Iterator 接口

Book 类

BookShelf 类

BookShelfIterator 类

Main 类

iterator 模式中各个角色

拓展思路的要点

不管如何变化,都可以使用 iterator

难以理解抽象类和接口

Aggregate 和 iterator 的对应

容易弄错“下一个”

还容易弄错“最后一个”

多个 iterator

迭代器的种类多种多样

不需要 deleteiterator

相关的设计模式

Iterator 模式—— 一个一个遍历

cd045eece9744e8f88a9a4cd6c449492.png


使用 Java 或者 C++ 语言显示数组 arr 中的元素时,我们可以使用以下这样的 for 循环语句来遍历数组:

for(int i = 0; i < arr.length; i++)
{
    System.out.printLn(arr[i]);
}

注意这段代码中的循环变量 i ,该变量的初始值是 0, 然后会递增为 1, 2, 3,…,程序则在每次 i 递增后都输出 arr[i] 。 我们在程序中经常会看到这样的 for 循环语句。


数组中保存了很多元素,通过指定数组下标,我们可以从中选择任意一个元素


for 语句中的 i++ 的作用是将 i 的值在每次循环后都自增 1,这样就可以访问数组中的下一个元素、下下个元素,也就实现了从头到尾逐一遍历数组元素的功能。


将这里的循环变量 i 的 作用抽象化,通用化之后形成的模式,在设计模式中称为 Iterator 模式。


Iterator 模式拥有在数据集合中按照顺序遍历集合。英语单词 Iterate 有反复做某件事情的意思,汉语称为“迭代器”。


示例程序

首先,让我们来看一段实现了 Iterator 模式的示例程序。这段示例程序的作用是将书(book)放置到书架(bookshelf)中,并将书的名字按顺序显示出来。

ccb47f79565348929cd583cbbb681842.png

Aggregate 接口

Aggreagate 接口是所要遍历的集合的接口。实现了该接口的类成为一个可以保存多个元素的集合,就像数组一样。Aggreagate 有“使聚集”“集合”的意思。

cb74c79ca57c48808c488fcde9f50208.png


类和接口的一览表:

4ac00adf89c942ff96084fb425028b8c.png

Aggregate 接口:

public interface Aggregate{
    public abstract Iterator iterator();
}

在 Aggregate 接口中声明的方法只有一个——iterator 方法。该方法会生成一个用于遍历集合的迭代器。想要遍历集合中的所有元素时,可以调用 iterator 方法来生成一个实现了 Iterator 接口的类的实例。


Iterator 接口

接下来我们看看 Iterator 接口。Iterator 接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。那么,在 iterator 接口中需要有哪些方法呢? iterator 接口的定义方式有很多种,这里编写最简单的接口方式:


public interface Iterator{
    public abstract boolean hasNext();
    public abstract Object next();
}

这里我们声明了两个方法,即判断是否存在下一个元素的 hasNext 方法和获取下一个元素的 next 方法。


hasNext() 方法返回值是 boolean 类型,其原因很容易理解。当集合中存在下一个元素时,该方法返回 TRUE;当集合中不存在下一个元素时,即已经遍历到集合末尾时,该方法返回 FALSE。 hasNext 方法主要用于循环终止条件判断。


这里必须说明一下 next 方法。该方法返回的类型是 Object ,这表明该方法返回的是集合中的一个元素。但是,next 方法的作用并非仅仅如此。为了能够在下一次调用 next 方法时能正确地返回下一个元素,该方法中还隐含着将迭代器移动到下一个元素的处理。说“隐含”是因为 Iterator 接口只知道方法名。要想知道 next 方法中具体怎么样的处理,还需要看一下实现 Iterator 接口的类(BooKShelfIterator)。


Book 类

Book 类是表示书的类。但是这个类的作用有限,它可以做的事情只有一件——通过 getNext 方法获取书的名字。书的名字是在外部调用 Book 类的构造函数并初始化 Book 类时,作为参数传递给 Book 类的。

public class Book{
    private String name;
    public Book(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

BookShelf 类

BookShelf 类是表示书架的类。由于需要将该类作为集合进行处理,因此它实现了 Aggregate 接口。代码中的 implements Aggregate 部分即表示这一点。此外,请注意在 BookShelf 类中还实现了 Aggregate 接口的 iterator 方法。

public class BookShelf implements Aggregate {
    private Book[] books;
    private int last = 0;
    public BookShelf(int maxsize) {
        this.books = new Book[maxsize];
    }
    public Book getBookAt(int index) {
        return books[index];
    }
    public void appendBook(Book book) {
        this.books[last] = book;
        last++;
    }
    public int getL ength() {
        return last;
    }
    public Iterator iterator() {
        return new BookShelfIterator(this);
    }
}

这个书架中定义了 books 字段,它是 Book 类型的数组。该数组的大小(maxsize)在生成 BookShelf 的实例时就被指定了。之所以将 books 字段的可见性设置为 private,是为了防止外部不小心改变了该字段的值。


接下来让我们看看 iterator 方法。该方法会生成并返回 BookShelfIterator 类的实例作为 BookShelf 类对应的 Iterator。当外部想要遍历书架时,就会调用这个方法。


BookShelfIterator 类

public class BookShelfIterator implements Iterator{
    private BookShelf bookShelf;
    private int index;
    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShlef;
        this.index = 0;
    }
    public boolean hasNext() {
        if(index < bookShelf.getLength()) {
            return true;
        }
        else {
            return false;
        }
    }
    public Object next() {
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}

因为 BookShelfIterator 类需要发挥 iterator 的作用,所以它实现了 iterator 接口。 bookshelf 字段表示 bookshelfiterator 所要遍历的书架,index 字段表示当前迭代器所指向的书的下标。


构造函数将接收到的 bookshelf 的实例保存在 bookshelf 字段中,并将 index 初始化为 0.


hasNext 方法是 iterator 接口中所声明的方法。该方法将判断书架中是否还有下一笔本书。


next 方法会返回迭代器当前所指向的书,并将迭代器指向下一本书。


Main 类

到这里为止,遍历书架的准备工作就完成了。接下来我们使用 Main 类来制作一个小书架:

public class Main() {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        Iterator it = bookShelf.iterator();
        while(it.hasNext())
        {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}

程序输出结果:

566c504ab34444c3bc507e80afdd941a.png

iterator 模式中各个角色

ccbf4e1186cd4041b027c750886a4e56.png

迭代器 iterator

具体的迭代器 ConcreteIterator

集合 Aggregate

具体的集合 ConcreteAggregate

拓展思路的要点

不管如何变化,都可以使用 iterator

为什么一定要考虑引入 iterator 这种复杂的设计模式呢? 如果是数组,直接使用 for 循环语句进行遍历就可以了啊,为什么要在集合之外引入 iterator 这个角色呢?


一个重要的理由是,引入 iterator 后可以将遍历与实现分离开,如下代码所示:

while ( it.hasNext())
{
    Book book = (Book)it.next();
    System.out.println(book.getName());
}

这里只是有了 iterator 的 hasNext 方法和 next 方法,并没有调用 bookshelf 的方法。也就是说这里的 while 循环并不依赖 bookshelf 的实现。


也就是说,如果后期放弃使用数组来管理书本,而是使用类似 vector 容器来管理,不管 bookshelf 如何变化,只要 bookshelf 的 iterator 方法能够正确返回 iterator 实例。即使不对上面的 while 循环做任何修改,代码也可以正常工作的。


这对于 bookshelf 调用者来说太方便了。设计模式的作用就是帮助我们编写可以服用的类,所谓“可以复用”就是指将类实现为 “组件”,当一个组件发生变化时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。


这样也就能理解为什么在实例程序中 iterator 方法返回值不是 bookshelfiterator 类型而是 iterator 类型了。这表明,这段程序就是使用 iterator 的方法进行变成,而不是 bookshelfiterator 的方法。


难以理解抽象类和接口

难以理解抽象类和接口的人常常使用 ConcreteAggreate 角色和 ConcreteIterator 角色编程,而不使用 Aggregate 接口和 iterator 接口,他们总想用具体的类来解决所有的问题。


但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。


这也是贯穿设计模式的思想。

Aggregate 和 iterator 的对应

请大家回忆一下我们是如何把 bookshelfiterator 类定义为 bookshelf 类的 concreteiterator 角色的。bookshelfiterator 类知道 bookshelf 是如何实现的。也正因为如此,我们才能调用用来获取下一本书的 getBookAt 方法。


returnCurrentElementAndAdvanceToNextPosition

也就是说,如果 bookshelf 的实现发生了变化,即 getBookAt 方法这个接口 (API) 发生变化时,我们必须修改 BookShelfIterator类。


正如 Aggregate 和 iterator 这两个接口是对应的一样, ConcreteAggregate 和 concreteIterator 这两个类也是对应的。


容易弄错“下一个”

在 iterator 模式的实现中,很容易在 next 方法上出错。该方法的返回值到底是应该指向当前元素还是当前元素的下一个元素呢?更详细地讲,next 方法的名字应该是下面这样的


也就是说,next 方法就是“返回当前的元素,并指向下一个元素”


还容易弄错“最后一个”

在iterator 模式中,不仅容易弄错“下一个”,还容易弄错“最后一个”。hasNext 方法在返回最后一个元素前会返回 TRUE,当返回最后一个元素后则返回 FALSE。稍不注意,就会无法正确地返回“最后一个”元素。


请大家将 hasNext 方法理解成“确认接下来是否可以调用 next 方法”的方法就是可以了。


多个 iterator

“将遍历功能置于 Aggregate 角色之外” 是 iterator 模式的一个特征。根据这个特征,可以针对一个 ConcreteAggregate 角色编写多个 ConcreteIterator 角色。


迭代器的种类多种多样

在示例程序中展现的 iterator 类只是很简单地从前向后遍历集合。其实,遍历的方法是多种多样的。


从最后开始向前遍历

既可以从前向后遍历,也可以从后向前遍历

指定下标进行“跳跃式”遍历

不需要 deleteiterator

Java 中有自动回收机制(GC),所以,在 iterator 中不需要与其对应的 deleteiterator 方法。


相关的设计模式

visitor 模式

iterator 模式是从集合中一个一个取出元素进行遍历,但是并没有在 iterator 接口中声明对取出元素进行何种处理。


visitor 模式则是在遍历元素集合的过程中,对元素进行相同的处理。在遍历集合的过程中对元素进行固定的处理是常有的需求。visitor 模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是 visitor 模式。


composite 模式

composite 模式是具有递归结构的模式,在其中使用 iterator 模式比较困难。


factor method 模式

在 iterator 方法中生成 iterator 的实例时可能会使用 factor method 模式。


相关文章
|
5天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
19天前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
12天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:如何提高代码的可维护性与扩展性在软件开发领域,PHP 是一种广泛使用的服务器端脚本语言。随着项目规模的扩大和复杂性的增加,保持代码的可维护性和可扩展性变得越来越重要。本文将探讨 PHP 中的设计模式,并通过实例展示如何应用这些模式来提高代码质量。
设计模式是经过验证的解决软件设计问题的方法。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理地使用设计模式可以显著提高代码的可维护性、复用性和扩展性。本文将介绍几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并通过具体的例子展示如何在PHP项目中应用这些模式。
|
9天前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
7天前
|
设计模式 Java
Java设计模式-工厂方法模式(4)
Java设计模式-工厂方法模式(4)
|
2月前
|
设计模式
设计模式-单一职责模式
设计模式-单一职责模式
|
2月前
|
设计模式 XML 存储
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
文章详细介绍了工厂方法模式(Factory Method Pattern),这是一种创建型设计模式,用于将对象的创建过程委托给多个工厂子类中的某一个,以实现对象创建的封装和扩展性。文章通过日志记录器的实例,展示了工厂方法模式的结构、角色、时序图、代码实现、优点、缺点以及适用环境,并探讨了如何通过配置文件和Java反射机制实现工厂的动态创建。
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
|
2月前
|
设计模式 XML Java
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
文章详细介绍了简单工厂模式(Simple Factory Pattern),这是一种创建型设计模式,用于根据输入参数的不同返回不同类的实例,而客户端不需要知道具体类名。文章通过图表类的实例,展示了简单工厂模式的结构、时序图、代码实现、优缺点以及适用环境,并提供了Java代码示例和扩展应用,如通过配置文件读取参数来实现对象的创建。
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
|
2月前
|
设计模式 uml C语言
设计模式----------工厂模式之简单工厂模式(创建型)
这篇文章详细介绍了简单工厂模式,包括其定义、应用场景、UML类图、通用代码实现、运行结果、实际应用例子,以及如何通过反射机制实现对象创建,从而提高代码的扩展性和维护性。
设计模式----------工厂模式之简单工厂模式(创建型)
|
2月前
|
设计模式 人工智能 达摩院
设计模式的基础问题之模板模式在软件开发中的优势是什么
设计模式的基础问题之模板模式在软件开发中的优势是什么