“迭代器”是程序员最不经常注意到的设计模式之一,因为通常它的实现直接内置在编程语言的标准工具中。 但是,这也是
“四人帮”,“ GoF”,“设计模式:可重用的面向对象软件的元素”一书中描述的行为模式之一,并且可以理解它的设备永远不会受伤,有时甚至可以有所帮助。
“迭代器”是一种顺序访问复合对象的所有元素(特别是容器类型,例如数组或集合)的方法。标准语言工具
创建某种
数组 :
let numbersArray = [0, 1, 2]
...然后以一个
周期 “遍历”它:
for number in numbersArray { print(number) }
...似乎是很自然的动作,尤其是对于像
Swift这样的现代编程语言而言。 但是,此简单操作的背后是实现Iterator模式原理的代码。
在“快速”中,为了能够使用
for
循环“迭代”变量,变量类型必须实现
Sequence
协议 。 除其他事项外,此协议要求类型具有
associatedtype
Iterator
类型,而
Iterator
则必须实现
IteratorProtocol
协议的要求,以及
工厂方法 makeIterator()
,该
方法为此类型返回一个特定的“迭代器”:
protocol Sequence { associatedtype Iterator : IteratorProtocol func makeIterator() -> Self.Iterator
反过来,
IteratorProtocol
协议仅包含一个方法
next()
,该方法返回序列中的下一个元素:
protocol IteratorProtocol { associatedtype Element mutating func next() -> Self.Element? }
听起来像“很多复杂的代码”,但实际上并非如此。 下面我们将看到这个。
Array
类型实现
Sequence
协议(尽管不是直接实现,而是通过
协议继承链实现:
MutableCollection
继承
Collection
要求,而后者继承
Sequence
要求),因此尤其是可以使用
for
-cycles迭代其实例。
自定义类型
需要执行什么操作才能迭代您自己的类型? 通常情况下,最容易举一个例子。
假设有一个表示书架的类型,该类型存储了某个类的某些实例集,而这些实例又代表了一本书:
struct Book { let author: String let title: String } struct Shelf { var books: [Book] }
为了能够“迭代”
Shelf
类的实例,该类必须满足
Sequence
协议的要求。 对于此示例,仅实现
makeIterator()
方法就足够了,尤其是因为其他协议要求具有
默认实现 。 此方法应返回实现
IteratorProtocol
协议的类型的实例。 幸运的是,对于Swift,这是非常简单的代码:
struct ShelfIterator: IteratorProtocol { private var books: [Book] init(books: [Book]) { self.books = books } mutating func next() -> Book? {
ShelfIterator
类型的
next()
方法
ShelfIterator
声明为
mutating
,因为类型实例必须以某种方式存储与当前迭代相对应的状态:
mutating func next() -> Book? { defer { if !books.isEmpty { books.removeFirst() } } return books.first }
此实现选项始终返回序列中的第一个元素;如果序列为空,则返回
nil
。
defer
块包装在用于更改迭代集合的代码中,该代码在方法返回后立即删除了最后一个迭代步骤的元素。
用法示例:
let book1 = Book(author: ". ", title: "") let book2 = Book(author: ". ", title: " ") let book3 = Book(author: ". ", title: " ") let shelf = Shelf(books: [book1, book2, book3]) for book in shelf { print("\(book.author) – \(book.title)") }
因为 所有使用的类型(包括
Array
基础
Shelf
)都是基于
值的
语义(与引用相反) ,您不必担心原始变量的值在迭代过程中会发生变化。 在基于链接语义处理类型时,应牢记这一点,并在创建自己的迭代器时将其考虑在内。
经典功能
“四人帮”一书中描述的经典“迭代器”,除了返回可迭代序列的下一个元素外,还可以随时返回迭代过程中的当前元素,可迭代序列的第一个元素以及“ flag”的值,该值指示是否元素相对于当前迭代步骤的迭代顺序。
当然,声明协议很容易,从而扩展了标准
IteratorProtocol
的功能:
protocol ClassicIteratorProtocol: IteratorProtocol { var currentItem: Element? { get } var first: Element? { get } var isDone: Bool { get } }
第一个和当前元素返回可选,因为 源序列可能为空。
简单的实现选项:
struct ShelfIterator: ClassicIteratorProtocol { var currentItem: Book? = nil var first: Book? var isDone: Bool = false private var books: [Book] init(books: [Book]) { self.books = books first = books.first currentItem = books.first } mutating func next() -> Book? { currentItem = books.first books.removeFirst() isDone = books.isEmpty return books.first } }
在模式的原始描述中,
next()
方法将迭代器的内部状态更改为下一个元素,并且其类型为
Void
,而当前元素由
currentElement()
方法返回。 在
IteratorProtocol
协议中,这两个功能就像组合在一起。
对
first()
方法的需求也令人怀疑,因为 迭代器不会更改原始序列,我们总是有机会访问其第一个元素(当然,如果有的话)。
并且,由于迭代结束时
next()
方法返回
nil
,所以
isDone()
方法也变得无用。
但是,出于学术目的,很有可能提出一个可以使用全部功能的功能:
func printShelf(with iterator: inout ShelfIterator) { var bookIndex = 0 while !iterator.isDone { bookIndex += 1 print("\(bookIndex). \(iterator.currentItem!.author) – \(iterator.currentItem!.title)") _ = iterator.next() } } var iterator = ShelfIterator(books: shelf.books) printShelf(with: &iterator)
将该
iterator
参数声明为
inout
因为 它的内部状态在函数执行期间发生变化。 当调用该函数时,迭代器实例不直接通过其自身的值传输,而是通过引用传输。
不使用调用
next()
方法的结果,该方法模拟了教科书实现的返回值的缺失。
而不是结论
这似乎是我这次要说的。 所有漂亮的代码并刻意编写!
我关于设计模式的其他文章: