Swift Universe中的迭代器设计模式
(本文最初是用俄语撰写的,并在此处发布。)
迭代器是设计模式之一,通常不为程序员所注意,因为其实现细节通常嵌入在编程语言的标准库中。 但是,它也是四人帮经典著作“设计模式:可重用的面向对象软件的元素”中描述的行为模式之一。 它的理解永远不会被淘汰,甚至可能会有所帮助。
迭代器是一种提供对复合对象的所有元素(通常是容器类型,例如数组和集合)的串行访问的方法。
语言内置资源
创建一个数组:
让numbersArray = [0,1,2]
…并循环遍历:
代表numbersArray {
打印(数量)
}
……这是很常见的事情,尤其是对于像Swift这样的现代编程语言而言。 但是,此功能由实现Iterator设计模式基础的代码支持。
在Swift中,一种类型必须符合Sequence
协议才能被for
循环迭代。 除其他事项外,此协议要求具有关联的Iterator
类型(必须符合IteratorProtocol
)并实现结构方法makeIterator()
(此类型返回此类型的特定迭代器):
协议序列{
relatedtype Iterator:IteratorProtocol
func makeIterator()-> Self.Iterator
//另一个要求在这里…
}
IteratorProtocol
包含唯一的方法— next()
,该方法按顺序返回以下对象:
协议IteratorProtocol {
关联类型元素
变异func next()-> Self.Element吗?
}
感觉好像很多复杂的代码,但实际上并非如此。 我们将在一段时间后确定。
例如, Array
转换为Sequence
(虽然不是直接,但通过协议继承链: MutableCollection
继承自Collection
,而Collection
继承自Sequence
),这就是为什么可以通过for
循环来标识其实例的原因。
用户类型
如果我们的类型必须是可迭代的,我们该怎么办? 像往常一样,显示示例更容易。
让我们定义用于存储书本的书架的类型:
结构书{
让作者:字符串
让标题:字符串
}
struct Shelf {
var图书:[图书]
}
让我们使此类型符合Sequence
。 对于此特定示例,只需执行makeIterator()
方法就足够了,因为其余需求具有默认实现。 makeIterator()
必须返回IteratorProtocol
实例。 幸运的是,在Swift情况下,很少有非常简单的代码:
struct ShelfIterator:IteratorProtocol {
私人var书籍:[书籍]
init(books:[Book]){
self.books =书籍
}
变异func next()->预订? {
// TODO:返回下一个基础Book元素。
}
}
扩展架:序列{
func makeIterator()-> ShelfIterator {
返回ShelfIterator(books:books)
}
}
由于迭代器实例必须以某种方式存储迭代状态,因此将next()
方法声明为变异的:
变异func next()->预订? {
推迟{
如果!books.isEmpty {books.removeFirst()}
}
返回书本
}
此实现选项始终返回序列中的第一个元素,如果该序列为空,则返回nil
。 defer
包装了迭代序列的变异代码,该代码在返回最后一步元素后立即将其删除。
使用示例:
let book1 = Book(作者:“ A。Brontë”,
标题:“怀德菲尔大厅的租户”)
let book2 = Book(作者:“ Ch。Brontë”,
标题:“简·艾尔”
let book3 = Book(作者:“ E。Brontë”,
标题:“呼啸山庄”)
让架子=架子(书籍:[book1,book2,book3])
用于书架上的书{
print(“ \(book.author)– \(book.title)”)
}
/ *
A.布隆特–维尔德霍尔厅的租户
频道 勃朗特–简爱
E.布隆特–呼啸山庄
* /
由于所有使用的类型(包括基础Array
)都是值类型(将其与引用类型相对),因此无需担心初始值在迭代时会发生突变。 在处理引用类型时,必须在实现迭代器时考虑到这一点。
经典功能
如《 Gang of Four》一书中所述,经典迭代器除上述内容外,还可以按顺序返回当前元素和第一个元素,并且指示仍然存在联合元素的标志。
像这样扩展IteratorProtocol
很容易:
协议ClassicIteratorProtocol:IteratorProtocol {
var currentItem:元素? {得到}
var first:元素? {得到}
var isDone:布尔{get}
}
(当前项和第一项是可选的,因为初始序列可能为空。)
一个实现可以是:
struct ShelfIterator:ClassicIteratorProtocol {
var currentItem:预定? =无
var first:预定?
var isDone:Bool = false
私人var书籍:[书籍]
init(books:[Book]){
self.books =书籍
首先= books.first
currentItem = books.first
}
变异func next()->预订? {
currentItem = books.first
books.removeFirst()
isDone = books.isEmpty
返回书本
}
}
原始模式描述具有next()
方法,该方法会使迭代器的内部状态发生变化,但不返回任何内容,而current元素则由currentElement()
方法返回。 IteratorProtocol
将这两个功能合并为一个。
first
几乎也不有用,因为iterator一定不能突变初始序列,并且它的第一个元素始终是可访问的(如果存在)。
当迭代过程结束时, next()
方法返回nil
,因此isDone()
也是无用的。
但是,出于学术目的,可以构成一种可以使用此经典功能的方法:
func printShelf(with iterator:inout ShelfIterator){
var bookIndex = 0
而!iterator.isDone {
bookIndex + = 1
print(“ \(bookIndex)。\(iterator.currentItem!.author)– \(iterator.currentItem!.title)”)
_ = iterator.next()
}
}
var iterator = ShelfIterator(books:rack.books)
printShelf(with:&iterator)
/ *
A.布隆特–维尔德霍尔厅的租户
频道 勃朗特–简爱
E.布隆特–呼啸山庄
* /
iterator
参数为inout
因为它在函数内发生了变异。 next()
结果未用于模仿不可返回的经典版本。
似乎就是今天。 感谢您的阅读,希望您学到了一些新知识! 如果您喜欢这篇文章,可以在这里和Twitter上关注我。
我关于设计模式的其他文章:
– iOS和Swift Universe中的访客设计模式