社内の「Scala 勉強会」で Phantom Type (幽霊型) という厨二心をくすぐる感じのデザインパターンを教えてもらったので、同じことを Swift でもやってみました。
インスタンスの状態を変数ではなく 型パラメータ として持つことで、状態チェックを実行時ではなく コンパイル時 に行えるというイカしたテクニックです。
class Status{}
class NotReady: Status{}
class Ready: Status{}
class Something<T: Status> {
static func createInstance() -> Something<NotReady> {
return Something<NotReady>()
}
func readify() -> Something<Ready> {
return Something<Ready>()
}
}
extension Something where T: Ready {
func shout() {
print("phantom types are awesome!")
}
}
Status
は NotReady
, Ready
をサブクラスに持つだけの空クラス(これが幽霊型)で、Something
は Status
型の型パラメータ T
を持っています。Something
には where T: Ready
という 型パラメータ制約付きの拡張 によって、shout()
という関数を追加しています。
createInstance()
でSomething<NotReady>
インスタンスが作られ、readify()
を呼ぶと新たに Something<Ready>
インスタンスが作られます。そうして作られた T: Ready
のインスタンスに対してのみ shout()
を呼ぶコードが書けるのです。
やってみましょう:
let s = Something.createInstance()
s.shout() // error: 'NotReady' is not a subtype of 'Ready'
ちゃんと コンパイルエラー が出ました!
一方で readify()
を呼ぶと…
let s = Something.createInstance()
s.readify().shout() // phantom types are awesome!
エラーもなく、ちゃんと実行もできました!
こうすることで コンパイラレベル で間違った状態での処理を防げるので、そのためのテストも作らなくて済むことになります。
Phantom Type カッコイイですね