既にDartにはAngularプロジェクト発のDIライブラリがあるのだけれど、
Webブラウザ上での動作時や公開ライブラリ等において、外部ライブラリに依存したくない場合や、そこまで重厚なDIが必要でない場合がままある。
そこで、Dart標準の言語機能で、DIっぽいことをあれこれ試した結果、implicit interfaceとmix-inを使ってCake Patternっぽいものができた。
すごく単純な例で解説。
class Entity {
String name;
Entity(this.name);
String toString() => "Entity:${this.name}";
}
class Repository {
List<Entity> getAll() =>
["entity1", "entity2"].map((s) => new Entity(s)).toList();
}
class Service {
Repository get repository => new Repository();
void execute() => print(repository.getAll());
}
EntityインスタンスをRepositoryインスタンスからgetAllメソッドで引っ張ってきてprintするServiceクラスのexecuteメソッドがある。
Serviceクラスのrepositoryプロパティは、プロダクション環境下では以下のクラスを参照したい。
class AwesomeRepository implements Repository {
List<Entity> getAll() =>
["awesomeEntity1", "awesomeEntity2"].map((s) => new Entity(s)).toList();
}
(まぁ実際は外部DBとかにでもアクセスすると思ってください)
もちろん、repositoryプロパティを可変にすれば自在にRepositoryの実装を変更できるのだろうが、そうするとServiceのインタフェースが汚れる。
Dartのimplicit interfaceは便利なのだけれど、意図せぬインタフェースを外部に提供してしまう場合もあるので、クラスやそのメンバーの可視性やオブジェクトの不変性には特に気をつけておきたい。
この例も同様、Serviceの状態は不変を保ちたいとする。
このような場合は、Mix-inを使用する。
以下のようなAwesomeRepositoryへの依存をサポートするためのMix-inを定義して、
abstract class AwesomeRepositoryMixin {
Repository get repository => new AwesomeRepository();
}
AwesomeRepositoryMixinとのmix-in applicationによって、AwesomeRepositoryへの依存を持つServiceを定義する。
class AwesomeService = Service with AwesomeRepositoryMixin;
(昔はtypedefキーワードだった。しみじみ)
これで、ServiceとAwesomeServiceそれぞれ同じexecuteメソッドを有するが、repositoryプロパティの参照先が変更されため、標準出力の内容がそれぞれで異なる結果が得られる。
main () {
new Service().execute();
new AwesomeService().execute();
}
ただ、この方法には、動的にrepositoryプロパティの参照先を変更できないという問題がある。
動的にしたい場合は、constructor injenctionなりproperty injectionなりを採用すればよいと思う。
それよりも、依存関係が複雑化した際のdart analyzerによる静的チェックの恩恵の方が個人的には大きく、この似非Cake patternの採用の動機になっている。
まとめ
- DIばりばりやりたい場合は、diパッケージ
- 動的にやりたい場合は、素直にconstructor injenctionかproperty injection
- 静的に依存関係を構築したい場合は、当記事のCake Pattern