迭代器模式是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。

背景

集合是编程中最常使用的数据类型之一。集合是一组对象的容器。

今天你需要使用深度优先算法来遍历树结构,明天可能会需要广度优先算法; 下周则可能会需要其他方式(比如随机存取树中的元素)。

  • 不断向集合中添加遍历算法会模糊其 “高效存储数据” 的主要职责。
  • 有些算法可能是根据特定应用订制的,将其加入泛型集合类中会显得非常奇怪。
  • 使用多种集合的客户端代码可能并不关心存储数据的方式。不过由于集合提供不同的元素访问方式,你的代码将不得不与特定集合类进行耦合。

解决方法

迭代器模式的主要思想是将集合的遍历行为抽取为单独的迭代器对象。

除实现自身算法外,迭代器还封装了遍历操作的所有细节,例如当前位置和末尾剩余元素的数量。 因此,多个迭代器可以在相互独立的情况下同时访问集合。

迭代器通常会提供一个获取集合元素的基本方法。客户端可不断调用该方法直至它不返回任何内容,这意味着迭代器已经遍历了所有元素。

所有迭代器必须实现相同的接口。这样一来,只要有合适的迭代器,客户端代码就能兼容任何类型的集合或遍历算法。

如果你需要采用特殊方式来遍历集合,只需创建一个新的迭代器类即可,无需对集合或客户端进行修改。

迭代器模式

适用场景

  • 当集合背后为复杂的数据结构,且希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。
    • 迭代器封装了与复杂数据结构进行交互的细节,为客户端提供多个访问集合元素的简单方法。
    • 这种方式不仅对客户端来说非常方便,而且能避免客户端在直接与集合交互时执行错误或有害的操作,从而起到保护集合的作用。
  • 使用该模式可以减少程序中重复的遍历代码。
    • 重要迭代算法的代码往往体积非常庞大。当这些代码被放置在程序业务逻辑中时,它会让原始代码的职责模糊不清,降低其可维护性。
    • 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。
  • 如果希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。
    • 该模式为集合和迭代器提供了一些通用接口。如果你在代码中使用了这些接口,那么将其他实现了这些接口的集合和迭代器传递给它时,它仍将可以正常运行。

实现步骤

  • 声明迭代器接口。该接口必须提供至少一个方法来获取集合中的下个元素
    • 为了使用方便,你还可以添加一些其他方法,例如获取前一个元素、记录当前位置和判断迭代是否已结束。
  • 声明集合接口并描述一个获取迭代器的方法。其返回值必须是迭代器接口。
    • 如果你计划拥有多组不同的迭代器,则可以声明多个类似的方法。
  • 为集合实现具体迭代器类。
    • 迭代器对象必须与单个集合实体链接。链接关系通常通过迭代器的构造函数建立。
  • 在你的集合类中实现集合接口
    • 针对特定集合为客户端代码提供创建迭代器的快捷方式。
    • 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
  • 检查客户端代码,使用迭代器替代所有集合遍历代码。
    • 每当客户端需要遍历集合元素时都会获取一个新的迭代器。

优缺点

优点

  • 单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类,你可对客户端代码和集合进行整理。
  • 开闭原则。你可实现新型的集合和迭代器并将其传递给现有代码,无需修改现有代码。
  • 可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态。
  • 可以暂停遍历并在需要时继续。

缺点

  • 如果你的程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
  • 对于某些特殊集合,使用迭代器可能比直接遍历的效率低。

与其它模式的关系

  • 可以使用迭代器模式来遍历组合模式树。
  • 可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 可以同时使用备忘录模式和迭代器来获取当前迭代器的状态,并且在需要的时候进行回滚。
  • 可以同时使用访问者模式和迭代器来遍历复杂数据结构,并对其中的元素执行所需操作,即使这些元素所属的类完全不同。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

type iterator interface {
	hasNext() bool
	getNext() *user
}

type collection interface {
  createIterator() iterator
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

type user struct {
	name string
	age  int
}

// userCollection 集合类,实现collection接口
type userCollection struct {
  users []*user
}

func (u *userCollection) createIterator() iterator {
  return &userIterator{
    users: u.users,
  }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

// userIterator 用户集合迭代器,实现iterator接口
type userIterator struct {
	index int
	users []*user
}

func (u *userIterator) hasNext() bool {
	if u.index < len(u.users) {
		return true
	}
	return false

}
func (u *userIterator) getNext() *user {
	if u.hasNext() {
		user := u.users[u.index]
		u.index++
		return user
	}
	return nil
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
	user1 := &user{
		name: "a",
		age:  30,
	}
	user2 := &user{
		name: "b",
		age:  20,
	}

	userCollection := &userCollection{
		users: []*user{user1, user2},
	}

	iterator := userCollection.createIterator()

	for iterator.hasNext() {
		user := iterator.getNext()
		fmt.Printf("User is %+v\n", *user)
	}
}

References

guru