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


Iterator 模式—— 一个一个遍历

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wL9t1J1C-1662629417110)(en-resource://database/1234:1)]

使用 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)中,并将书的名字按顺序显示出来。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7a2ipdj-1662629417111)(en-resource://database/1236:1)]

Aggregate 接口

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AqEPPtvs-1662629417111)(en-resource://database/1238:1)]

类和接口的一览表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHmRuqT4-1662629417112)(en-resource://database/1240:1)]

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());
        }
    }
}

程序输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1EVKL5S-1662629417112)(en-resource://database/1242:1)]

iterator 模式中各个角色

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RQyVD7j-1662629417112)(en-resource://database/1244:1)]

  • 迭代器 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 方法。

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

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

容易弄错“下一个”

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

returnCurrentElementAndAdvanceToNextPosition

也就是说,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 模式。

猜你喜欢

转载自blog.csdn.net/qq_37596943/article/details/126768915