Help us understand the problem. What is going on with this article?

JavaのIteratorパターンをswiftで頑張って実装した

結城先生のJava言語で学ぶデザインパターン入門を読んでいます。
最初の章でIteratorパターンが出てきます。「あ!これswiftではprotocolでうまく表現すればいいやつだ!」と意気揚々と始めます。そして30秒ぐらいで気づきます。

関連型を持つIteratorProtocolはプロパティとして持てない。

つまりこういうのがswiftの言語仕様では出来ないです。

dekinai.swift
protocol Aggregate {
    var iterator: IteratorProtocol { get }
}

どう実装しようかな...。と悩んだのですが、RxSwift等で使われている「抽象クラスと総称型を組み合わせて規定し、実装は全部fatalError()にする。」というパターンを最初に試してみました。

出来上がったのは以下です。

教本通り

associatedtypeを持つprotocolをプロパティで持てないので、class Iterator<T>: IteratorProtocol のように一旦クラスに適合させます。

ただ、この時点でElementの具体型を決定するのは遅延したいため総称型を利用します。Aggregate側でも同じように総称型を取り、Iterator側に渡します。

iteratorPattern.swift
import Foundation

class Aggregate<T> {

    var iterater: Iterator<T> {
        fatalError()
    }

}

class Iterator<T>: IteratorProtocol {

    typealias Element = T

    func next() -> T? {
        fatalError()
    }

    var hasNext: Bool {
        fatalError()
    }

}

struct Book {
    let name: String
}

final class BookShelfIterator: Iterator<Book> {
    private let bookShelf: BookShelf
    private var index: Int = 0

    init(bookShelf: BookShelf) {
        self.bookShelf = bookShelf
    }

    override var hasNext: Bool {
        if index < bookShelf.length {
            return true
        } else {
            return false
        }
    }

    override func next() -> Book? {
        let book = bookShelf[index]
        index += 1
        return book
    }

}

final class BookShelf: Aggregate<Book> {
    private var books: [Book] = []

    subscript(index: Int) -> Book {
        return books[index]
    }

    func append(_ newElement: Book) {
        books.append(newElement)
    }

    var length: Int {
        return books.count
    }

    override var iterater: Iterator<Book> {
        return BookShelfIterator(bookShelf: self)
    }

}

func main() {
    let bookShelf = BookShelf()
    bookShelf.append(Book(name: "ハリーポッターと賢者の石"))
    bookShelf.append(Book(name: "ハリーポッターと秘密の部屋"))
    bookShelf.append(Book(name: "ハリーポッターとアズカバンの囚人"))
    bookShelf.append(Book(name: "ハリーポッターと炎の御ブレッド"))

    let iterator = bookShelf.iterater
    while iterator.hasNext {
        let book = iterator.next()
        print(book?.name)
    }

}

main()

// Optional("ハリーポッターと賢者の石")
// Optional("ハリーポッターと秘密の部屋")
// Optional("ハリーポッターとアズカバンの囚人")
// Optional("ハリーポッターと炎の御ブレッド")

ちょっとカスタマイズ

while をあまり使いたくないので、Sequence プロトコルにも適合させて for item in items で表現できるようにします。

updated.swift
...)

class Iterator<T>: IteratorProtocol, Sequence {

    typealias Element = T

    func next() -> T? {
        fatalError()
    }

    var hasNext: Bool {
        fatalError()
    }

}

...)

final class BookShelfIterator: Iterator<Book> {
    private let bookShelf: BookShelf
    private var index: Int = 0

    init(bookShelf: BookShelf) {
        self.bookShelf = bookShelf
    }

    override var hasNext: Bool {
        if index < bookShelf.length {
            return true
        } else {
            return false
        }
    }

    override func next() -> Book? {
        guard hasNext else {
            return nil
        }
        let book = bookShelf[index]
        index += 1
        return book
    }

}

...)

func main() {
    let bookShelf = BookShelf()
    bookShelf.append(Book(name: "ハリーポッターと賢者の石"))
    bookShelf.append(Book(name: "ハリーポッターと秘密の部屋"))
    bookShelf.append(Book(name: "ハリーポッターとアズカバンの囚人"))
    bookShelf.append(Book(name: "ハリーポッターと炎の御ブレッド"))

    let iterator = bookShelf.iterater
    for book in iterator {
        print(book.name)
    }

}

書いてみたのはいいですが、「冗長すぎひん?」と思ったのでswiftっぽく書き直してみました。

swiftっぽく書いてみた

これでええんちゃう?

Sequence プロトコルにも、makeIterator() という関数が規定されているため同じように扱えます。 Aggregate(集約)Sequence(連続) ではやってることは同じですが意味が異なるので、ちょっと意味的には不安な実装ではありますが...。

swiftIterator.swift
struct Book {
    let name: String
}

struct BookShelfIterator: IteratorProtocol {
    typealias Element = Book
    private let bookShelf: BookShelf
    private var index: Int = 0

    init(bookShelf: BookShelf) {
        self.bookShelf = bookShelf
    }

    var hasNext: Bool {
        if index < bookShelf.length {
            return true
        } else {
            return false
        }
    }

    mutating func next() -> Book? {
        guard hasNext else {
            return nil
        }
        let book = bookShelf[index]
        index += 1
        return book
    }

}

struct BookShelf: Sequence {
    typealias Iterator = BookShelfIterator

    private var books: [Book] = []

    subscript(index: Int) -> Book {
        return books[index]
    }

    mutating func append(_ newElement: Book) {
        books.append(newElement)
    }

    var length: Int {
        return books.count
    }

    func makeIterator() -> BookShelfIterator {
        return BookShelfIterator(bookShelf: self)
    }

}

func main() {
    var bookShelf = BookShelf()
    bookShelf.append(Book(name: "ハリーポッターと賢者の石"))
    bookShelf.append(Book(name: "ハリーポッターと秘密の部屋"))
    bookShelf.append(Book(name: "ハリーポッターとアズカバンの囚人"))
    bookShelf.append(Book(name: "ハリーポッターと炎の御ブレッド"))

    // 本棚から本を取り出すという自然な表現になっていると思う
    for book in bookShelf {
        print(book.name)
    }

}

実装してみて

RxSwiftではなんだかよく分からない総称型の使い方をしていて「なんだかよく分からないなぁ」と常々思っていたのですが、デザパタを表現しようと思った時にswiftの言語仕様では不都合が出るので、それを回避しつつ表現するための工夫だったのかな?という気づきがありました。

抽象クラスと総称型をうまく使えば、言語仕様の差分を吸収してパターンの表現をしていけそうな事がわかって勉強になりました。でも、できる限りprotocol orientedで表現したいですね。

コンパイラや存在型などに詳しくなりたいなぁという感想です。

2019/07/06追記

記述可能でした :bow:

IteratorPattern.swift
protocol Aggregate {
    associatedtype Iterator: IteratorProtocol
    var iterator: Iterator { get }
}

struct Book {
    let name: String
}

struct BookShelfIterator: IteratorProtocol {
    private let bookShelf: BookShelf
    private var index: Int = 0

    init(bookShelf: BookShelf) {
        self.bookShelf = bookShelf
    }

    var hasNext: Bool {
        if index < bookShelf.length {
            return true
        } else {
            return false
        }
    }

    mutating func next() -> Book? {
        let book = bookShelf[index]
        index += 1
        return book
    }

}

struct BookShelf: Aggregate {
    typealias Iterator = BookShelfIterator
    private var books: [Book] = []

    subscript(index: Int) -> Book {
        return books[index]
    }

    mutating func append(_ newElement: Book) {
        books.append(newElement)
    }

    var length: Int {
        return books.count
    }

    var iterator: BookShelfIterator {
        return BookShelfIterator(bookShelf: self)
    }

}

func main() {
    var bookShelf = BookShelf()
    bookShelf.append(Book(name: "ハリーポッターと賢者の石"))
    bookShelf.append(Book(name: "ハリーポッターと秘密の部屋"))
    bookShelf.append(Book(name: "ハリーポッターとアズカバンの囚人"))
    bookShelf.append(Book(name: "ハリーポッターと炎の御ブレッド"))

    var iterator = bookShelf.iterator
    while iterator.hasNext {
        let book = iterator.next()
        print(book?.name)
    }

}

main()

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away