零基础设计模式——行为型模式 - 迭代器模式

发布于:2025-06-11 ⋅ 阅读:(19) ⋅ 点赞:(0)

第四部分:行为型模式 - 迭代器模式 (Iterator Pattern)

现在我们来学习迭代器模式。这个模式非常常见,它提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

  • 核心思想:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

迭代器模式 (Iterator Pattern)

“提供一种方法顺序访问一个聚合对象 (Aggregate) 中各个元素,而又不暴露该对象的内部表示。” (Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.)

想象一下你看电视换频道:

  • 电视机 (Aggregate Object):它有很多频道(元素集合)。这些频道可能是以某种内部方式存储的(比如一个列表、一个数组、或者更复杂的数据结构)。
  • 遥控器上的“下一个频道”/“上一个频道”按钮 (Iterator):你不需要知道电视机内部是如何存储和管理这些频道的。你只需要按“下一个频道”,遥控器(迭代器)就会帮你找到并切换到下一个可用的频道。

迭代器模式允许你遍历集合的元素,而无需关心集合是如何实现的(例如,是 ArrayListLinkedListHashSet 还是自定义的集合)。

1. 目的 (Intent)

迭代器模式的主要目的:

  1. 访问聚合对象的元素而无需暴露其内部结构:客户端代码可以使用统一的迭代器接口来遍历不同类型的集合,而不需要知道集合的具体实现细节。
  2. 支持多种遍历方式:可以为同一个聚合对象提供多种不同的迭代器(例如,正向迭代器、反向迭代器、跳跃迭代器等)。
  3. 简化聚合类的接口:聚合类本身不需要包含遍历其元素的方法,这些职责被移到了迭代器类中。
  4. 统一的遍历接口:为遍历不同的聚合结构提供一个统一的接口。

2. 生活中的例子 (Real-world Analogy)

  • MP3播放器的播放列表

    • 播放列表 (Aggregate Object):包含多首歌曲。
    • “下一首”/“上一首”按钮 (Iterator):让你顺序播放歌曲,而不用关心歌曲是如何存储的。
  • 书本的目录或页码

    • 书 (Aggregate Object):包含很多页内容。
    • 通过目录查找章节,或一页一页翻阅 (Iterator):让你能够顺序或按特定方式访问书的内容。
  • 餐厅服务员按顺序上菜

    • 一桌菜 (Aggregate Object):包含多道菜品。
    • 服务员按照上菜顺序(凉菜 -> 热菜 -> 主食 -> 汤 -> 甜点)逐个端上 (Iterator):服务员知道上菜的顺序和当前应该上哪道菜。

3. 结构 (Structure)

迭代器模式通常包含以下角色:

  1. Iterator (迭代器接口/抽象类):定义访问和遍历元素的接口,通常包含如 hasNext()(或 hasMore())、next()(或 currentItem())等方法。有时也可能包含 first()isDone() 等。
  2. ConcreteIterator (具体迭代器):实现 Iterator 接口。它负责跟踪聚合中的当前位置,并知道如何计算下一个元素。
  3. Aggregate (聚合接口/抽象类):定义创建相应迭代器对象的接口,通常是一个方法如 createIterator()iterator()
  4. ConcreteAggregate (具体聚合):实现 Aggregate 接口。它返回一个对应的 ConcreteIterator 实例。它持有实际的元素集合。
  5. Client (客户端):通过 Aggregate 接口获取 Iterator 对象,然后使用 Iterator 接口来遍历聚合中的元素。
    在这里插入图片描述
    工作流程
  • 客户端向一个具体聚合对象请求一个迭代器。
  • 具体聚合对象实例化一个具体迭代器,并将自身的引用(或其内部数据结构的引用)传递给迭代器。
  • 客户端使用迭代器的 hasNext()next() 方法来顺序访问聚合中的元素。
  • 迭代器负责维护遍历的状态(如当前索引)。

4. 适用场景 (When to Use)

  • 当你需要访问一个聚合对象的内容而无需暴露其内部表示时。
  • 当你需要支持对聚合对象的多种遍历方式时(例如,正序、倒序、过滤等)。
  • 当你需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当聚合对象的接口不应包含遍历操作,以保持其职责单一时。
  • 在几乎所有需要遍历集合的场景中,尤其是在标准库中(如 Java Collections Framework, Go’s range keyword over slices/maps, C# IEnumerable/IEnumerator)。

5. 优缺点 (Pros and Cons)

优点:

  1. 封装性好:访问聚合对象的内容而无需暴露其内部细节,客户端不依赖于聚合的特定实现。
  2. 支持多种遍历:可以为同一个聚合提供多种不同的迭代器。
  3. 简化聚合接口:聚合类不需要承担遍历的职责。
  4. 遍历逻辑与聚合对象分离:迭代逻辑封装在迭代器中,使得两者可以独立变化。
  5. 统一遍历接口:客户端可以用同样的方式遍历不同的聚合结构。

缺点:

  1. 增加了类的数量:对于简单的聚合,引入迭代器可能会增加不必要的复杂性(尽管在很多语言中,迭代器是内建的或由库提供)。
  2. 迭代器通常是轻量级的,但创建迭代器对象本身仍有开销。

6. 实现方式 (Implementations)

让我们以一个简单的书架 (BookShelf) 和书 (Book) 的例子来说明。

元素类 (Book)
// book.go
package aggregate

// Book 元素类
type Book struct {
	Name string
}

func NewBook(name string) *Book {
	return &Book{Name: name}
}
// Book.java
package com.example.aggregate;

public class Book {
    private String name;

    public Book(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
聚合接口 (Aggregate)
// aggregate.go (Aggregate interface)
package aggregate

import "../iterator"

// Aggregate 聚合接口
type Aggregate interface {
	CreateIterator() iterator.Iterator
}
// Aggregate.java (Aggregate interface)
package com.example.aggregate;

import com.example.iterator.Iterator;

public interface Aggregate {
    Iterator<Book> createIterator();
}
具体聚合 (BookShelf - ConcreteAggregate)
// bookshelf.go (ConcreteAggregate)
package aggregate

import "../iterator"

// BookShelf 具体聚合
type BookShelf struct {
	books []*Book
	last  int // 当前书的数量,也作为下一个可添加位置的索引
}

func NewBookShelf(maxSize int) *BookShelf {
	return &BookShelf{
		books: make([]*Book, maxSize),
		last:  0,
	}
}

func (bs *BookShelf) AddBook(book *Book) {
	if bs.last < len(bs.books) {
		bs.books[bs.last] = book
		bs.last++
	}
	// else: bookshelf is full, handle error or ignore
}

func (bs *BookShelf) GetBookAt(index int) *Book {
	if index >= 0 && index < bs.last {
		return bs.books[index]
	}
	return nil
}

func (bs *BookShelf) GetLength() int {
	return bs.last
}

// CreateIterator 实现 Aggregate 接口
func (bs *BookShelf) CreateIterator() iterator.Iterator {
	// Go 中,具体迭代器需要知道具体聚合的类型以访问其内部数据
	// 所以这里直接传递 bs 给 BookShelfIterator 的构造函数
	return iterator.NewBookShelfIterator(bs) // 假设 BookShelfIterator 在 iterator 包中
}
// BookShelf.java (ConcreteAggregate)
package com.example.aggregate;

import com.example.iterator.BookShelfIterator;
import com.example.iterator.Iterator;
import java.util.ArrayList;
import java.util.List;

public class BookShelf implements Aggregate {
    private List<Book> books;

    public BookShelf() {
        this.books = new ArrayList<>();
    }

    public void addBook(Book book) {
        this.books.add(book);
    }

    public Book getBookAt(int index) {
        if (index >= 0 && index < books.size()) {
            return books.get(index);
        }
        return null;
    }

    public int getLength() {
        return books.size();
    }

    @Override
    public Iterator<Book> createIterator() {
        return new BookShelfIterator(this); // 'this' is the ConcreteAggregate
    }
}
迭代器接口 (Iterator)
// iterator.go (Iterator interface)
package iterator

// Iterator 迭代器接口
type Iterator interface {
	HasNext() bool
	Next() interface{} // 返回 interface{} 以便通用,具体使用时需要类型断言
}
// Iterator.java (Iterator interface - generic)
package com.example.iterator;

public interface Iterator<T> {
    boolean hasNext();
    T next();
}
具体迭代器 (BookShelfIterator - ConcreteIterator)
// bookshelf_iterator.go (ConcreteIterator)
package iterator

import "../aggregate" // 导入 aggregate 包以获取 BookShelf 类型

// BookShelfIterator 具体迭代器
type BookShelfIterator struct {
	bookShelf *aggregate.BookShelf // 持有具体聚合的引用
	index     int
}

// NewBookShelfIterator 构造函数,接收具体聚合对象
func NewBookShelfIterator(bookShelf *aggregate.BookShelf) *BookShelfIterator {
	return &BookShelfIterator{
		bookShelf: bookShelf,
		index:     0,
	}
}

func (bsi *BookShelfIterator) HasNext() bool {
	return bsi.index < bsi.bookShelf.GetLength()
}

func (bsi *BookShelfIterator) Next() interface{} {
	if bsi.HasNext() {
		book := bsi.bookShelf.GetBookAt(bsi.index)
		bsi.index++
		return book
	}
	return nil // Or panic, or return an error
}
// BookShelfIterator.java (ConcreteIterator)
package com.example.iterator;

import com.example.aggregate.Book;
import com.example.aggregate.BookShelf;

public class BookShelfIterator implements Iterator<Book> {
    private BookShelf bookShelf; // Holds the ConcreteAggregate
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < bookShelf.getLength();
    }

    @Override
    public Book next() {
        if (hasNext()) {
            Book book = bookShelf.getBookAt(index);
            index++;
            return book;
        }
        return null; // Or throw NoSuchElementException as in java.util.Iterator
    }
}
客户端使用
// main.go (示例用法)
/*
package main

import (
	"./aggregate"
	"./iterator"
	"fmt"
)

func main() {
	bookShelf := aggregate.NewBookShelf(4)
	bookShelf.AddBook(aggregate.NewBook("Around the World in 80 Days"))
	bookShelf.AddBook(aggregate.NewBook("Bible"))
	bookShelf.AddBook(aggregate.NewBook("Cinderella"))
	bookShelf.AddBook(aggregate.NewBook("Daddy-Long-Legs"))

	it := bookShelf.CreateIterator()

	fmt.Println("Iterating through BookShelf:")
	for it.HasNext() {
		book, ok := it.Next().(*aggregate.Book) // 类型断言
		if ok && book != nil {
			fmt.Println(book.Name)
		}
	}

	// Go 的 range 关键字本身就是一种迭代器模式的体现
	fmt.Println("\nUsing Go's built-in range (for slices, not directly for our custom BookShelf without extra methods):")
	// 要让 range 直接作用于 BookShelf,BookShelf 需要实现特定的接口或提供一个可供 range 的 slice
	// 例如,可以给 BookShelf 添加一个方法 `GetBooksSlice() []*Book`
	// booksSlice := bookShelf.GetBooksSlice() // 假设有这个方法
	// for _, book := range booksSlice {
	//    fmt.Println(book.Name)
	// }
}
*/
// Main.java (示例用法)
/*
package com.example;

import com.example.aggregate.Book;
import com.example.aggregate.BookShelf;
import com.example.iterator.Iterator;

public class Main {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf();
        bookShelf.addBook(new Book("Design Patterns: Elements of Reusable Object-Oriented Software"));
        bookShelf.addBook(new Book("Effective Java"));
        bookShelf.addBook(new Book("Clean Code"));
        bookShelf.addBook(new Book("The Pragmatic Programmer"));

        Iterator<Book> iterator = bookShelf.createIterator();

        System.out.println("Iterating through BookShelf:");
        while (iterator.hasNext()) {
            Book book = iterator.next();
            System.out.println(book.getName());
        }

        // Java's enhanced for loop (for-each loop) uses the Iterator pattern implicitly
        // if the collection implements java.lang.Iterable
        System.out.println("\nIterating using Java's enhanced for loop (if BookShelf implements Iterable<Book>):");
        // To use the enhanced for loop, BookShelf would need to implement java.lang.Iterable<Book>
        // public class BookShelf implements Aggregate, Iterable<Book> {
        //     @Override
        //     public java.util.Iterator<Book> iterator() {
        //         return new BookShelfIterator(this); // Assuming BookShelfIterator implements java.util.Iterator
        //     }
        // }
        // Then you could do:
        // for (Book book : bookShelf) {
        //     System.out.println(book.getName());
        // }
    }
}
*/

关于语言内建迭代器

  • Java: java.util.Iterator 接口和 java.lang.Iterable 接口是迭代器模式的核心。集合类(如 ArrayList, HashSet)都实现了 Iterable,它们的 iterator() 方法返回一个 Iterator
  • Go: range 关键字为数组、切片、字符串、map 和 channel 提供了内建的迭代机制。对于自定义类型,通常会提供一个返回 channel 的方法,或者实现类似 Next()/HasNext() 的方法对,但不像 Java 那样有统一的迭代器接口强制。
  • Python: 迭代器协议 (__iter__()__next__()) 是语言的核心部分。
  • C#: IEnumerableIEnumerator 接口(以及 yield return 关键字)用于实现迭代。

这些内建机制使得在这些语言中使用迭代器模式非常自然和普遍。

7. 总结

迭代器模式是一种基础且广泛应用的设计模式,它优雅地解决了在不暴露集合内部结构的情况下顺序访问集合元素的问题。通过将遍历逻辑封装在独立的迭代器对象中,它增强了代码的封装性、灵活性和可维护性。几乎所有的现代编程语言都在其标准库中深度集成了迭代器模式,使其成为处理集合数据的标准方式。