Swift宇宙中的迭代器架构模式

“迭代器”是程序员最不经常注意到的设计模式之一,因为通常它的实现直接内置在编程语言的标准工具中。 但是,这也是“四人帮”,“ 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 // Another requirements go here… } 

反过来, 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? { // TODO: Return next underlying Book element. } } extension Shelf: Sequence { func makeIterator() -> ShelfIterator { return ShelfIterator(books: books) } } 

ShelfIterator类型的next()方法ShelfIterator声明为mutating ,因为类型实例必须以某种方式存储与当前迭代相对应的状态:

 mutating func next() -> Book? { defer { if !books.isEmpty { books.removeFirst() } } return books.first } 

此实现选项始终返回序列中的第一个元素;如果序列为空,则返回nildefer块包装在用于更改迭代集合的代码中,该代码在方法返回后立即删除了最后一个迭代步骤的元素。

用法示例:

 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) /* 1. .  –  2. .  –   3. .  –   */ 

将该iterator参数声明为inout因为 它的内部状态在函数执行期间发生变化。 当调用该函数时,迭代器实例不直接通过其自身的值传输,而是通过引用传输。

不使用调用next()方法的结果,该方法模拟了教科书实现的返回值的缺失。

而不是结论


这似乎是我这次要说的。 所有漂亮的代码并刻意编写!

我关于设计模式的其他文章:

Source: https://habr.com/ru/post/zh-CN437614/


All Articles