Anyパターンについて考えてみる
「Swiftの標準ライブラリだと付属型を持ったプロトコルを変数に格納するため、慣例的にAny
なんとかってクラスを用意してる」というところまでが話の前提です。
Any
クラス実装者の責務
例によって、Pokemon
プロトコルで考えてみます。
ポケモンではすべての技(move)を使いきった場合、攻撃を選択しても「わるあがき」しかできなくなります。これはすべてのポケモン共通の挙動なので、Protocol Extensionで実装すると楽ですね。
protocol Pokemon { associatedtype PokemonType func attack(move:PokemonType) func struggle() } extension Pokemon { func struggle() { // 相手にダメージを与えて自分のHPを1/4削る } }
「わるあがき」は技タイプなしの特殊攻撃なので(ゴーストタイプにも当たる)、PokemonType
には依存しないものとして実装されるとします。
残念ながら、現世代までのポケモンで「わるあがき」で特殊な挙動をする個体はいないみたいですが、将来的に特殊な実装をされる可能性を考慮すると、適切に実装を退避させる必要があります。
class AnyPokemon <PokemonType>: Pokemon { private let _attack: ((PokemonType) -> Void) private let _struggle: (() -> Void) required init<U:Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) { _attack = pokemon.attack _struggle = pokemon.struggle } func attack(type:PokemonType) { return _attack(type) } // 省略した場合はデフォルト実装が呼ばれてしまう! func struggle() { return _struggle() } }
省略するとデフォルト実装になってしまう、というのがポイントです。
Any
タイプを作る開発者は、Protocol Extensionによるデフォルト実装も含めた、そのプロトコルの全ての処理を知る必要がある のです。
AnySequence
を実現している、_SequenceBox
の場合どうなってるのかというと、必ずしも全ての処理が委譲されるわけではないらしく、たとえばdropFirst(_ n: Int)
は委譲されるのだけれど、dropFirst()
(引数なし版)は委譲されません。
dropFirst()
を変な処理でオーバーライドすると壊れます。やる人いないと思いますけど。
Protocol ExtensionでAny
パターンを壊す
SequenceType
にProtocol Extensionでメソッドを足してみます。
extension SequenceType { func hogehoge () { print("do") } } let sequence = AnySequence(["a", "b"]) sequence.hogehoge() // do
AnySequence
もSequenceType
に準拠しているので、Protocol Extensionの恩恵を受けます。ここまでは期待通りです。
ただし、独自のSequenceType
を作って、Protocol Extensionで追加したものと同名のメソッドを実装すると破綻します。
class MySequence : SequenceType { private var values: [String] = ["a", "b"] func generate() -> IndexingGenerator<[String]>{ return values.generate() } func hogehoge() { print("inherit") } } let original = MySequence() let boxed = AnySequence(original) original.hogehoge() // inherit boxed.hogehoge() // do original.dropFirst().hogehoge() // do
これは_SequenceBox
の実装を考えれば自明ですね。dropFirst()
でもデフォルト実装が呼び出されてしまうのは、dropFirst()
がAny
でラップする実装だからですね。
// 何も考えずに、Github上で master branch を見ていたので、 // Swift 2.2じゃなくて3.0のコードですが、大差ないので気にしない @warn_unused_result public func dropFirst() -> SubSequence { return dropFirst(1) } @warn_unused_result public func dropFirst(_ n: Int) -> AnySequence<Iterator.Element> { precondition(n >= 0, "Can't drop a negative number of elements from a sequence") if n == 0 { return AnySequence(self) } return AnySequence(_DropFirstSequence(_iterator: makeIterator(), limit: n)) }
結論
CollectionType
なんかの組み込み型に対してProtocol Extensionする場合、Any
で型消去されたときに、Protocol Extensionのデフォルト実装しか呼び出されない点について注意が必要?
これは言語仕様的に防御した方がいいような気がする…何のメリットもないし。