Scalaで作成中のアプリケーションで、DI(Dependency Injection)をしようとしているという話。 簡単に書けるだろうと高をくくっていたら、無惨にも書けなくなってしまったという話でもある。
DIとは?
DI -- Dependency Injectionとは、訳せば依存性の注入。依存しているクラス同士の依存関係をハードコードするのではなく、外部 -- 例えば呼び出し元 -- から「注入」することによって抽象度を上げ、テストなどを容易に行うための考え方だ。
往々にして、プログラム中に「仮定」を置きたくなるという事が発生する。理想的に動作する、仮のスタブのようなクラスが必要になることがあるものだ。テストを複雑にしやすいネットワーク関連のクラス、再現が困難な特定の手順を踏まなければ提供されないインスタンス。これらをテストのたびにインスタンス化するのは面倒極まる。そういうわけで、とりあえず希望される動作をしてくれるクラスを作る事になる。
trait Networking { def write(arr: Array[Byte]) def read(): Array[Byte] } class DummyNetworking extends Networking { def write(arr: Array[Byte]) = {} def read(): Array[Byte] = List(0, 0, 0, 0, 0).toArray[Byte] } class RealNetworking extends Networking { def write(arr: Array[Byte] = /* snip */ def read(): Array[Byte] = /* snip */ } class SomeTask(network: Networking) { def doSomeThing = { network.write(hogehoge) val piyopiyo = newtork.read() } } // 実用 val task = new SomeTask(new RealNetworking) task.doSomething // テスト val task = new SomeTask(new DummyNetworking) task.doSomething
以上の例では、コンストラクタによって外部から依存性が注入され、SomeTaskはNetworkingがどのように実装されているかを知ることなく、テストや実際の実行をこなすことができるようになった。 これがDI -- Dependency Injectionである。
実際のDI
理屈の上ではだいたいそういう感じなのだが、自分のクラスばかり使うとも限らない。外部のクラスを使用することもあるし。そうすると外部のクラスに勝手に継承関係を持たせることができるとは限らない、というかほぼ無理。これを解決しないと、外部クラスが絡むクラスのテストができなくなりそう。
型クラスでDIできると聞いた(気がする)のでちょっと調べてみる。