結城先生のJava言語で学ぶデザインパターン入門を読んでいます。
最初の章でIteratorパターンが出てきます。「あ!これswiftではprotocolでうまく表現すればいいやつだ!」と意気揚々と始めます。そして30秒ぐらいで気づきます。
関連型を持つIteratorProtocolはプロパティとして持てない。
つまりこういうのがswiftの言語仕様では出来ないです。
protocol Aggregate {
var iterator: IteratorProtocol { get }
}
どう実装しようかな...。と悩んだのですが、RxSwift等で使われている「抽象クラスと総称型を組み合わせて規定し、実装は全部fatalError()にする。」というパターンを最初に試してみました。
出来上がったのは以下です。
教本通り
associatedtypeを持つprotocolをプロパティで持てないので、class Iterator<T>: IteratorProtocol
のように一旦クラスに適合させます。
ただ、この時点でElement
の具体型を決定するのは遅延したいため総称型を利用します。Aggregate側でも同じように総称型を取り、Iterator側に渡します。
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
で表現できるようにします。
...)
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(連続)
ではやってることは同じですが意味が異なるので、ちょっと意味的には不安な実装ではありますが...。
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追記
記述可能でした
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()