はじめに
ちょっとしたアプリやライブラリを作っていて、コンポーネントの依存性を管理したくなる時があります。例えば、「あるコンポーネントは毎回作成したいけど他のコンポーネントはシングルトンにしたい。そして、それらのコンポーネント間に依存関係がある。」と言った場合です。
もちろん、世の中には依存性管理のためのライブラリがいくつもあるのでそれらを活用するのも良いでしょう。しかし、わざわざライブラリを導入するほどでもないということも多いかと思います。そんな時に私が使っているお手軽な方法を紹介したいと思います。
前提
Controller、Service、Repositoryという3つのコンポーネントがあって、それらに依存関係があるとします。なお、Controllerは毎回新規のインスタンスが生成され、ServiceとRepositoryはシングルトンとして扱われるものとします。
シーケンス図で表すとこんな感じでしょうか。
+------------+ +------------+ +------------+
| Controller | | Service | | Repository |
+-----+------+ +-----+------+ +-----+------+
| | |
| execute() | |
+---------------->| execute() |
| |---------------->|
| |<----------------+
|<----------------+ |
コードも示してみます。
各コンポーネントの中では、自身の文字列表現と後続のコンポーネントの実行結果を連結しています。これは、呼び出しごとに新インスタンスが生成されているのか、同じインスタンスが使われているのか後ほど確認するためです。
class Controller(private val service: Service) {
fun execute(): String {
return this.toString() + " -> " + service.execute()
}
}
class Service(private val repository: Repository) {
fun execute(): String {
return this.toString()+ " -> " + repository.execute()
}
}
class Repository {
fun execute(): String {
return this.toString()
}
}
解決方法
私はこんな感じのコードを書いてコンポーネントの依存関係を管理します。
object Container {
// 呼ばれるたびに新規インスタンス作成
val controller get() = Controller(service)
// シングルトン
val service by lazy { Service(repository) }
// シングルトン
val repository by lazy { Repository() }
}
ポイント
このクラスのポイントは3つです。
- 依存先コンポーネントはプロパティ経由で得る
-
get()
を使うことでControllerのインスタンスは毎回生成 -
lazy
を使うことでSeriverとRepositoryのインスタンスは1度だけ生成(実質的にシングルトン)
1度だけだ生成するならlazy
を使う代わりにval service = Service(repository)
のようにプロパティのinitializerを使えばいいのではないかと思ったかもしれません。しかし、これはうまくいかないのです。参照しているrepository
プロパティはまだ宣言されていないのでコンパイルエラーになります。もちろん先にrepository
を宣言した上でservice
プロパティを宣言すればinitializerを使う方法でも構わないのですが、lazy
を使うことでプロパティの宣言順序を気にする必要がなくなります。
Containerの利用例
上述のContainerを利用してみましょう。
fun main() {
val controller1 = Container.controller
println(controller1.execute())
val controller2 = Container.controller
println(controller2.execute())
}
実行結果はこんな感じになります。
example.Controller@6ce253f1 -> example.Service@53d8d10a -> example.Repository@e9e54c2
example.Controller@65ab7765 -> example.Service@53d8d10a -> example.Repository@e9e54c2
以下のことがわかりますね。
- Controllerは1度目と2度目の呼び出しで異なるインスタンス
- ServiceとRepositoryは1度目と2度目の呼び出しで同じインスタンス
おわりに
get()
やlazy
など、標準で組み込まれている機能を使うことでコンポーネントの依存性管理が簡単にできることを紹介しました。
もし良ければお試しください。
この方法ではうまくいかないケースがあるなどのフィードバックも歓迎です。