Go语言设计模式:迭代器模式详解
文章目录
- 一、迭代器模式概述
 - 1.1 什么是迭代器模式?
 - 1.2 为什么需要迭代器模式?(解决的问题)
 - 1.3 迭代器模式的结构
 - 1.4 优缺点分析
 - 1.5 适用场景
 
- 二、Go语言实现:自定义集合的迭代器
 - 2.1 步骤 1: 定义元素和迭代器接口
 - 2.2 步骤 2: 创建具体聚合和具体迭代器
 - 2.3 步骤 3: 客户端代码
 - 2.4 Go语言的惯用方式:`range` 关键字
 - 2.5 完整代码
 - 2.6 执行结果
 - 2.7 结果分析
 
一、迭代器模式概述
1.1 什么是迭代器模式?
迭代器模式(Iterator Pattern) 是一种行为型设计模式,它提供一种方法来顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示。
 简单来说,迭代器模式就是为集合(如列表、数组、树等)创建一个“遍历器”。客户端代码通过这个遍历器来访问集合中的元素,而无需关心集合内部是如何存储这些元素的(是数组、链表还是其他数据结构)。
一个绝佳的比喻: 想象一下看电视。
- 电视台(聚合 Aggregate):拥有所有的节目(元素)。
 - 遥控器(迭代器 Iterator):你可以按“下一个频道”按钮来切换节目。
 - 你(客户端 Client):只需要操作遥控器,就可以一个接一个地看节目,完全不需要知道电视台是如何存储和播放这些节目的(是磁带、硬盘还是数字信号)。遥控器为你屏蔽了这些复杂性。
 
1.2 为什么需要迭代器模式?(解决的问题)
迭代器模式主要解决以下问题:
- 分离集合的遍历行为:将遍历数据的职责从集合对象中分离出来,放入一个独立的迭代器对象中。这符合单一职责原则。
 - 简化集合对象的接口:集合对象只需要负责存储和管理元素,不需要提供多种遍历方式(正序、倒序、过滤等),这些复杂的遍历逻辑可以由不同的迭代器来实现。
 - 支持多种遍历方式:一个集合可以同时拥有多个迭代器,每个迭代器可以有不同的遍历策略。
 - 隐藏内部结构:客户端代码不依赖于集合的内部实现。你可以随时改变集合的内部数据结构(比如从数组改为链表),而只要迭代器接口不变,客户端代码就无需修改。
 
1.3 迭代器模式的结构
- Iterator (迭代器接口):定义了访问和遍历元素所需的方法,通常包括 
Next()(移动到下一个元素)、CurrentItem()(获取当前元素) 和IsDone()(判断是否遍历结束)。 - ConcreteIterator (具体迭代器):实现迭代器接口,并跟踪当前遍历的位置。
 - Aggregate (聚合接口):定义一个创建迭代器对象的方法。
 - ConcreteAggregate (具体聚合):实现聚合接口,返回一个与自身兼容的具体迭代器实例。
UML 结构图: 
+----------------+      +-----------------+
|     Client     |----->|    Aggregate    |
|----------------|      |-----------------|
|                |      | + CreateIterator() |
+----------------+      +-----------------+^                      ^| uses                 | implements|                      |
+----------------+      +-----------------+
|    Iterator    |<-----|ConcreteAggregate|
|----------------|      |-----------------|
| + Next()       |      | - items         |
| + CurrentItem()|      | + CreateIterator()|
| + IsDone()     |      +-----------------+
+----------------+              ^|                      || implements           |v                      |
+----------------+              |
|ConcreteIterator|-------------+
|----------------|
| - position     |
| - aggregate    |
| + Next()       |
| + CurrentItem()|
| + IsDone()     |
+----------------+
 
1.4 优缺点分析
优点:
- 支持多种遍历方式:一个集合可以有多个迭代器,每个迭代器实现不同的遍历逻辑。
 - 简化聚合接口:聚合对象不需要关心遍历细节,代码更简洁。
 - 隐藏内部结构:客户端代码与集合的内部实现解耦。
缺点: - 增加类的数量:对于简单的集合,引入迭代器模式可能会增加不必要的复杂性(一个聚合类 + 一个迭代器类)。
 - 设计复杂度:对于非常简单的遍历需求,可能会感觉“杀鸡用牛刀”。
 
1.5 适用场景
- 当你需要访问一个聚合对象的内容,而又不想暴露其内部表示时。
 - 当你需要为同一个聚合对象提供多种不同的遍历方式时。
 - 当你希望提供一个统一的接口来遍历不同类型的聚合对象时。
 
二、Go语言实现:自定义集合的迭代器
场景:我们创建一个自定义的 Bookshelf(书架)集合,它内部用一个切片来存储 Book(书)。我们为它实现一个迭代器,可以按顺序遍历书架上的所有书。
2.1 步骤 1: 定义元素和迭代器接口
// Element 集合中的元素
type Book struct {Name string
}
// Iterator 迭代器接口
type Iterator interface {HasNext() boolNext() *Book
}
// Aggregate 聚合接口
type Aggregate interface {CreateIterator() Iterator
}
 
注意:Go 语言中更常见的迭代器接口是 Next() (T, bool),它将 HasNext 和 Next 合并了。这里为了更贴近经典模式,我们先分开定义。*
2.2 步骤 2: 创建具体聚合和具体迭代器
// ConcreteAggregate: 书架
type Bookshelf struct {books []*Book
}
func NewBookshelf() *Bookshelf {return &Bookshelf{books: make([]*Book, 0)}
}
func (b *Bookshelf) AddBook(book *Book) {b.books = append(b.books, book)
}
// CreateIterator 创建一个与该书架兼容的迭代器
func (b *Bookshelf) CreateIterator() Iterator {return &BookshelfIterator{bookshelf: b, position: 0}
}
// ConcreteIterator: 书架迭代器
type BookshelfIterator struct {bookshelf *Bookshelfposition  int
}
func (i *BookshelfIterator) HasNext() bool {return i.position < len(i.bookshelf.books)
}
func (i *BookshelfIterator) Next() *Book {if !i.HasNext() {return nil}book := i.bookshelf.books[i.position]i.position++return book
}
 
2.3 步骤 3: 客户端代码
客户端通过聚合接口创建迭代器,并使用迭代器来遍历集合。
func main() {// 1. 创建一个书架(聚合对象)bookshelf := NewBookshelf()bookshelf.AddBook(&Book{Name: "Go语言设计与实现"})bookshelf.AddBook(&Book{Name: "代码整洁之道"})bookshelf.AddBook(&Book{Name: "重构:改善既有代码的设计"})// 2. 通过书架创建一个迭代器iterator := bookshelf.CreateIterator()// 3. 使用迭代器遍历书架fmt.Println("--- 开始遍历书架 ---")for iterator.HasNext() {book := iterator.Next()if book != nil {fmt.Printf("找到书籍: %s\n", book.Name)}}fmt.Println("--- 遍历结束 ---")
}
 
2.4 Go语言的惯用方式:range 关键字
 
值得一提的是,Go 语言的语言特性本身就内置了对迭代模式的支持。如果你的自定义集合实现了以下方法:
type MyCollection struct {// ... 内部数据
}
func (c *MyCollection) Len() int           { /* ... */ }
func (c *MyCollection) Less(i, j int) bool { /* ... */ }
func (c *MyCollection) Swap(i, j int)      { /* ... */ }
 
那么你就可以使用标准库的 sort.Sort() 来排序,或者如果你实现了一个返回 chan T 的方法,就可以用 for ... range 来遍历。
 更直接地,你可以为你的集合实现一个可以用于 for...range 的迭代器:
// 为 Bookshelf 实现一个可以用于 range 的迭代器
func (b *Bookshelf) Iterator() func() (*Book, bool) {index := 0return func() (*Book, bool) {if index >= len(b.books) {return nil, false}book := b.books[index]index++return book, true}
}
// 客户端使用方式
func mainWithRange() {bookshelf := NewBookshelf()bookshelf.AddBook(&Book{Name: "Go语言设计与实现"})bookshelf.AddBook(&Book{Name: "代码整洁之道"})// 使用 for...range 风格的迭代器for book, ok := bookshelf.Iterator(); ok; book, ok = bookshelf.Iterator() {fmt.Printf("找到书籍: %s\n", book.Name)}
}
 
注意:上面的 for...range 示例为了简化逻辑,每次循环都重新创建了迭代器状态,这不符合迭代器的初衷。一个更符合 Go 惯用法的方式是返回一个 chan,但这超出了经典迭代器模式的范畴。
关键点:虽然 Go 有 range,但理解经典的迭代器模式仍然非常重要,因为它能让你在无法使用 range 的复杂场景下(如需要多种遍历策略、或者在不支持 range 的语言中)自己构建出灵活、可复用的遍历逻辑。
2.5 完整代码
将以下代码保存为 main.go 文件。
package main
import "fmt"
// =============================================================================
// 1. 定义元素和迭代器接口
// =============================================================================
// Element 集合中的元素
type Book struct {Name string
}
// Iterator 迭代器接口
type Iterator interface {HasNext() boolNext() *Book
}
// Aggregate 聚合接口
type Aggregate interface {CreateIterator() Iterator
}
// =============================================================================
// 2. 创建具体聚合和具体迭代器
// =============================================================================
// ConcreteAggregate: 书架
type Bookshelf struct {books []*Book
}
func NewBookshelf() *Bookshelf {return &Bookshelf{books: make([]*Book, 0)}
}
func (b *Bookshelf) AddBook(book *Book) {b.books = append(b.books, book)
}
// CreateIterator 创建一个与该书架兼容的迭代器
func (b *Bookshelf) CreateIterator() Iterator {return &BookshelfIterator{bookshelf: b, position: 0}
}
// ConcreteIterator: 书架迭代器
type BookshelfIterator struct {bookshelf *Bookshelfposition  int
}
func (i *BookshelfIterator) HasNext() bool {return i.position < len(i.bookshelf.books)
}
func (i *BookshelfIterator) Next() *Book {if !i.HasNext() {return nil}book := i.bookshelf.books[i.position]i.position++return book
}
// =============================================================================
// 3. 客户端代码
// =============================================================================
func main() {// 1. 创建一个书架(聚合对象)bookshelf := NewBookshelf()bookshelf.AddBook(&Book{Name: "Go语言设计与实现"})bookshelf.AddBook(&Book{Name: "代码整洁之道"})bookshelf.AddBook(&Book{Name: "重构:改善既有代码的设计"})// 2. 通过书架创建一个迭代器iterator := bookshelf.CreateIterator()// 3. 使用迭代器遍历书架fmt.Println("--- 开始遍历书架 ---")for iterator.HasNext() {book := iterator.Next()if book != nil {fmt.Printf("找到书籍: %s\n", book.Name)}}fmt.Println("--- 遍历结束 ---")
}
 
2.6 执行结果
运行上述代码后,你将在终端看到以下输出:
--- 开始遍历书架 ---
找到书籍: Go语言设计与实现
找到书籍: 代码整洁之道
找到书籍: 重构:改善既有代码的设计
--- 遍历结束 ---
 
2.7 结果分析
- 客户端与聚合的解耦: 
- 在 
main函数中,我们只与Bookshelf(聚合)和Iterator(迭代器)接口交互。 - 我们完全没有关心 
Bookshelf内部是用切片 ([]*Book) 还是其他数据结构来存储书籍。如果将来我们将其改为链表,只要CreateIterator方法返回一个能正常工作的迭代器,main函数中的遍历代码就无需任何修改。 
 - 在 
 - 遍历逻辑的封装: 
- 遍历的细节(如何移动到下一个元素、如何判断是否结束)被完全封装在了 
BookshelfIterator结构体中。 Bookshelf结构体本身只负责管理书籍的增删,不关心如何被遍历,这符合单一职责原则。
 - 遍历的细节(如何移动到下一个元素、如何判断是否结束)被完全封装在了 
 - 迭代过程: 
for循环首先调用iterator.HasNext()来检查是否还有下一个元素。- 如果有,则调用 
iterator.Next()来获取当前元素并将迭代器的内部指针向前移动。 - 这个过程一直持续,直到 
HasNext()返回false,循环结束。 
 
这个例子清晰地展示了迭代器模式如何将一个集合的内部表示与外部遍历行为分离开来,从而提高了代码的灵活性和可维护性。
总结:迭代器模式是一个非常基础且广泛使用的设计模式。它将数据集合与遍历行为解耦,使得代码更加灵活和可维护。在 Go 语言中,虽然 for...range 提供了语法糖,但理解迭代器模式的原理能帮助你更好地设计自己的数据结构和 API,并在需要时实现更强大的自定义遍历逻辑。
