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()}
}
返回书本
}

此实现选项始终返回序列中的第一个元素,如果该序列为空,则返回nildefer包装了迭代序列的变异代码,该代码在返回最后一步元素后立即将其删除。

使用示例:

  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中的访客设计模式