图解设计模式——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 模式。


相关文章
|
1天前
|
设计模式 XML 数据格式
【设计模式】探秘迭代器模式:如何像数星星一样遍历你的数据集?
【设计模式】探秘迭代器模式:如何像数星星一样遍历你的数据集?
5 0
|
1天前
|
设计模式 API
【设计模式】适配器和桥接器模式有什么区别
【设计模式】适配器和桥接器模式有什么区别
8 1
|
1天前
|
设计模式
【设计模式】张一鸣笔记:责任链接模式怎么用?
【设计模式】张一鸣笔记:责任链接模式怎么用?
11 1
|
1天前
|
设计模式 uml
【设计模式】建造者模式就是游戏模式吗?
【设计模式】建造者模式就是游戏模式吗?
11 0
|
1天前
|
设计模式 Java uml
【设计模式】什么是工厂方法模式?
【设计模式】什么是工厂方法模式?
7 1
|
1天前
|
设计模式 uml
【设计模式】一文搞定简单工厂模式!
【设计模式】一文搞定简单工厂模式!
8 2
|
1天前
|
设计模式 JavaScript 前端开发
js设计模式-观察者模式与发布/订阅模式
观察者模式和发布/订阅模式是JavaScript中的两种设计模式,用于处理对象间的通信和事件处理。观察者模式中,一个主题对象状态改变会通知所有观察者。实现包括定义主题和观察者对象,以及在主题中添加、删除和通知观察者的功能。发布/订阅模式则引入事件管理器,允许发布者发布事件,订阅者通过订阅接收通知。
|
1天前
|
设计模式 前端开发 Java
19:Web开发模式与MVC设计模式-Java Web
19:Web开发模式与MVC设计模式-Java Web
24 4
|
1天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
38 2
|
1天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式