Swiftで試すデザインパターン - ArrayやDictionaryを支えるIteratorパターン
教科書:Java言語で学ぶデザインパターン入門
「数学ガール」で有名な結城浩先生の「Java言語で学ぶデザインパターン入門1」を購入しました2。
せっかくの機会なので、普段使っているSwiftで実装しながら、勉強ノートを作っていこうと思います。
本記事は「第1章 Iterator 1つ1つ数え上げる」を参考に作成しました。
Iteratorパターンの嬉しさがなんとなくわかってきた!
はじめてIteratorパターンに触れたとき(JavaScriptのIteratorでした)は、何が嬉しいのかまったくわかりませんでした。
今回、自分で手を動かして具体的に実装したり、SwiftのSequence
やmap
のソースコードを眺めているうちにIteratorパターンの嬉しさがなんとなく実感できた気がします。
同じような気持ちを抱いている方の参考になると嬉しいです!
Iteratorパターンとは?
ArrayやDictionaryのように、何かの集まりの全体を順番にスキャンしていくときに使われるデザインパターンです。
少し長い引用になりますが、「Java言語で学ぶデザインパターン入門1」では以下のように説明されています。
for文のi++でiを1ずつ増加させていくと……配列arrの要素全体を最初から順番にスキャン(走査)していることになります。ここで使われている変数iの働きを抽象化し、一般化したものを、デザインパターンでは、
Iteratorパターン
と呼んでいます。
Iteratorパターンとは、何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのものです。iterate(イテレート)という英単語は何かを「繰り返す」という意味です。3
Iteratorパターンの登場人物
Iteratorインターフェース
要素のスキャンを実行する型が持つインターフェースです。
いろいろな流派があるそうですが、シンプルなものとして、以下のようなprotocolが考えられます。
protocol MyIteratorProtocol {
// 配列などが保持する要素の型
associatedtype Element
// 次の要素があるかどうかを返すメソッド
func hasNext() -> Bool
// スキャン中に次の要素を返すメソッド
mutating func next() -> Element?
}
Aggregate(集合)インターフェース
配列やDictionaryのように「何か」の集まりを保持する型が持つインターフェースで、iteratorを生成するメソッドを持ちます。
protocol AggregateProtocol {
// 配列などが保持する要素の型
associatedtype Element
// iteratorメソッドが返すIteratorの型、Elementが一致する必要がある
associatedtype Iterator: MyIteratorProtocol where Iterator.Element == Element
// iteratorを生成するメソッド
func iterator() -> Iterator
}
ConcreteIterator / ConcreteAggregate
IteratorインターフェースとAggregateインターフェースを実装(準拠)した具体的な型たちです。
具体例:Iteratorパターンを実装してみる
ここでは例として、Bird型の集合を保持するBirdCage型とそのIteratorであるBirdCageIteratorを実装していきます。
Bird
名前を保持するだけのstructです。
struct Bird {
let name: String
}
BirdCage
Birdの集合を保持するstructを定義します。
AggregateProtocolに準拠するために、associatedTypeのaliasを設定して、iteratorメソッドを実装します。
ここでは簡単のためにBirdの集合自体は配列で保持します。
struct BirdCage: AggregateProtocol {
// associatedTypeに具体的な型を設定する
typealias Element = Bird
typealias Iterator = BirdCageIterator
// iteratorを返すメソッドを実装する
func iterator() -> Iterator {
BirdCageIterator(birds: birds)
}
// ここでは簡単のためにArrayとして集合を保持する
private var birds: [Bird] = []
// birdを鳥籠に入れるメソッド
mutating func put(bird: Bird) {
birds.append(bird)
}
// indexを指定してbirdの様子を覗く
func peek(birdOf index: Int) -> Bird? {
index < birds.count ? birds[index] : nil
}
}
BirdCateIterator
BirdCateのIteratorを定義します。
MyIteratorProtocolに準拠するために、associatedTypeのaliasを設定して、hasNext/nextメソッドを実装します。
struct BirdCageIterator: MyIteratorProtocol {
// associatedTypeに具体的な型を設定する
typealias Element = Bird
// スキャンするための集合を配列で保持する
private let birds: [Bird]
private var index: Int = 0
init(birds: [Bird]) {
self.birds = birds
}
func hasNext() -> Bool {
index < birds.count
}
// メソッドが呼ばれたら、自身のindexをひとつ進める
mutating func next() -> Bird? {
let bird = index < birds.count ? birds[index] : nil
index += 1
return bird
}
}
使ってみる
集合の各要素をprintする関数を定義して呼び出します。
ポイントは、Aggregate: AggregateProtocol
のように型パラメータを使って関数を定義することです。
このように実装された関数は、Iteratorパターンで実装されている型(AggregateProtocol
に準拠している型)すべてに対して使うことができます。
func printElements<Aggregate: AggregateProtocol>(of aggregate: Aggregate) {
var iterator = aggregate.iterator()
while iterator.hasNext() {
let element = iterator.next()
print(String(describing: element!))
}
}
var cage = BirdCage()
cage.put(bird: Bird(name: "Budgerigar"))
cage.put(bird: Bird(name: "Cockatiel"))
cage.put(bird: Bird(name: "Parakeet"))
cage.put(bird: Bird(name: "Parrot"))
printElements(of: cage)
// Bird(name: "Budgerigar")
// Bird(name: "Cockatiel")
// Bird(name: "Parakeet")
// Bird(name: "Parrot")
Iteratorパターンの何が嬉しいのか?
具体的な実装(の詳細)とは関係なく使用できる
先ほどのprintElements
関数は、具体的なBirdCage
型に対してではなく、「AggregateProtocol
に準拠したAggregate
一般」に対して実装されています。
そのため、具体的なBirdCage
やBirdCageIterator
が、iterator
やnext
メソッドをどのように実装しているかとは全く関係なく、printElements
が引数aggregate: Aggregate
を使うことができます。
printElementsを便利に使う
この利点を享受しつつ、printElements
をより便利に使うためには、printElements
をAggregateProtocol
のextensionに定義してやります。
extension AggregateProtocol {
func printEelements() {
var iterator = self.iterator()
while iterator.hasNext() {
let element = iterator.next()
print(String(describing: element!))
}
}
}
var cage = BirdCage()
cage.put(bird: Bird(name: "Budgerigar"))
cage.put(bird: Bird(name: "Cockatiel"))
// AggregateProtocolに準拠しているすべてのstructからprintElementsを呼ぶことができる!
cage.printElements()
ArrayやDictionary, StringでもIteratorパターンが使われている
Swift標準のIteratorProtocol
実はSwiftには標準でIteratorProtocol
というものが用意されています4。
public protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
Iteratorパターンで実装された Sequence protocol
そして、ArrayやDictionary, Stringなどの様々なstructが準拠しているSequence
プロトコルがIteratorProtocol
を内部で利用しています5。
public protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
__consuming func makeIterator() -> Iterator
...
}
この部分の定義は、本記事で作成したAggregateProtocol
そのものです。
まさにこのSequence
というプロトコルがIteratorパターンを使って実装されているのです。
mapの実装にもIteratorが使われている
たとえば、Arrayではお馴染みのmap
メソッドは、実はSequence
プロトコルのextensionに実装されています。
そのため、ArrayやRangeはもちろん、DictionaryやStringについてもmap
メソッドを呼び出すことができます。
[1, 2, 3].map { $0 + 1 }
(0...2).map { $0 * 2 }
["key1": 1, "key2": 2].map { (k, i) in i }
"Hello, world!".map { $0.uppercased() }
そして、map
メソッドの内部ではiteratorが使われています6。
ArrayやDictionary, Range, Stringの内部実装やそれぞれに定義されたIteratorの実装はそれぞれに異なるはずですが、IteratorProtocol
のインターフェースを通じてmap
メソッドをひとつの実装で済ますことができるわけです。
extension Sequence {
...
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
result.reserveCapacity(initialCapacity)
var iterator = self.makeIterator()
// Add elements up to the initial capacity without checking for regrowth.
for _ in 0..<initialCapacity {
result.append(try transform(iterator.next()!))
}
// Add remaining elements, if any.
while let element = iterator.next() {
result.append(try transform(element))
}
return Array(result)
}
}
おわりに
Iteratorパターンの嬉しさが実感できたでしょうか?
次回のパターンもお楽しみに!
環境
- Xcode 11.6
- Swift 5.2.4
参考
-
-
2020年9月1日現在、サマーセール中でkindle版が半額です! ↩
-
増補改訂版 Java言語で学ぶデザインパターン入門 第1章 Iterator 1つ1つ数え上げる ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L177 ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L325 ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L600 ↩
-
2020年9月1日現在、サマーセール中でkindle版が半額です! ↩
-
増補改訂版 Java言語で学ぶデザインパターン入門 第1章 Iterator 1つ1つ数え上げる ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L177 ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L325 ↩
-
https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L600 ↩
Author And Source
この問題について(Swiftで試すデザインパターン - ArrayやDictionaryを支えるIteratorパターン), 我々は、より多くの情報をここで見つけました https://qiita.com/turara/items/4648a3789dae299e2fcb著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .