Kotlin で “Thin Cake” パターンを真似る
こんにちは!
- @nobuoka です
- Java サーバーサイド / web フロントエンド / Android アプリ
- Kotlin 1.1 気になりますね!
- 今日は Kotlin でのお手製 DI
- “Thin Cake” パターンについて
- Kotlin での疑似モジュールミックスイン合成
- 実用的というより興味本位なので気軽に聞いてください
Dependency Injection (DI)
- 依存オブジェクトを注入するパターン
- Android アプリ開発でもよく使われる
- 様々な手法
- Guice のような DI コンテナ、Dagger のようなライブラリ
- Cake パターンのようなパターン ← 今日はココ
- コンストラクタで依存オブジェクトを注入 (手動で単純)
Cake パターン
- Scala におけるお手製 DI のパターン
- Odersky 先生らの論文発祥
- コンポーネント再利用のための 3 つの抽象化
- 抽象型メンバー
- 自分型アノテーション
- モジュールミックスイン合成
モジュールの合成
- Trait (実装を持てるインターフェイスぽいもの) をモジュールとして使う
- モジュール
- 何らかの機能 (オブジェクト) を提供
- 別のモジュールのオブジェクトに依存
- モジュールの組み合わせ → オブジェクトグラフ
モジュール
trait FooModule {
// This module provides:
lazy val foo = "foo"
lazy val fooBar = s"($foo+$bar)"
lazy val fooBarBaz = s"($fooBar+$barBaz+$bazFoo)"
// This module depends on:
def bar: String
def barBaz: String
def bazFoo: String
}
オブジェクトグラフ
object FooBarBazComponent
extends FooModule with BarModule with BazModule {
val baz = "baz"
}
Kotlin で “Thin Cake” パターン
インターフェイスのデフォルト実装!
モジュール
interface FooModule {
// This module provides:
val foo get() = "foo" // Kotlin 1.1
val fooBar get() = "(foo+$bar)"
val fooBarBaz get() = "($fooBar+$barBaz+$bazFoo)"
// This module depends on:
val bar: String
val barBaz: String
val bazFoo: String
}
オブジェクトグラフ
object FooBarBazComponent : FooModule, BarModule, BazModule {
override val baz = "baz"
}
が…… 駄目っ……!
Object 'FooBarBazComponent' must override public open val foo: String defined in FooModule because it inherits multiple interface methods of it
デリゲーションなら?
class ObjectGraph() :
FooModule by FooModuleImpl(),
BarModule by BarModuleImpl(),
BazModule by BazModuleImpl()
// これではだめ。
// なぜなら FooModuleImpl が BarModule によって提供される機能に
// 依存したくでもできない (参照できない) ため。
- 2 つの問題
- モジュールの実装がオブジェクトグラフを参照できない
XxxModule インターフェイスにモジュールの依存も宣言されている
依存を分離し、依存をパラメータに
interface FooModule {
// This module provides:
val foo: String
// ... (略) ...
// This module depends on:
interface Dependencies {
val bar: String
// ... (略) ...
}
class Impl(private val d: Lazy<Dependencies>) : FooModule {
override val foo get() = "foo"
// ... (略) ...
}
}
オブジェクトグラフ
interface FooBarBazComponent {
interface Dependencies {
val baz: String
}
class ObjectGraph(self: Lazy<ObjectGraph>, d: Dependencies) :
Dependencies by d,
FooModule by FooModule.Impl(self), FooModule.Dependencies,
BarModule by BarModule.Impl(self), BarModule.Dependencies,
BazModule by BazModule.Impl(self), BazModule.Dependencies
companion object {
fun create(d: Dependencies): ObjectGraph =
AtomicReference<ObjectGraph>().let { r ->
ObjectGraph(lazy { r.get() }, d).apply { r.set(this) } }
}
}
疑似的に “Thin Cake” パターンを実現できた
- 疑似的にモジュールミックスイン合成を実現
- Kotlin のデリゲーション
Lazy によりデリゲーション先オブジェクトに自分自身を渡す (無理やり感)
XxxProvider というインターフェイスを経由して依存関係を記述
interface FooProvider {
val foo: String
}
// ... こんな感じで ...
interface FooModule :
// 提供するオブジェクト一覧
FooProvider, FooBarProvider, FooBarBazProvider {
interface Dependencies :
// 依存するオブジェクト一覧
BarProvider, BazBarProvider
class Impl(private val d: Lazy<Dependencies>) : FooModule {
// ... 実装 ...
}
}
インジェクトされるオブジェクトのコンストラクタ
class Hoge(d: Dependencies) {
interface Dependencies :
FooProvider
fun useFoo() {
// d.foo を使う
d.foo + "bar"
}
}
// そうするとモジュール側の実装は単純になる
module HogeModule : HogeProvider {
/* 略 */
class Impl(private val d: Lazy<Dependencies>) : HogeModule {
override val hoge by lazy { Hoge(d.value) }
}
}