Kotlin で “Thin Cake” パターンを真似る

信岡 裕也 (リクルートマーケティングパートナーズ)
@nobuoka / id:nobuoka
R.kt #1

本発表は、1 月 20 日の Kyobashi.dex #4 での発表とほぼ同じです。

こんにちは!

  • @nobuoka です
    • キッズリー開発
    • サーバーサイド (Java, Kotlin) / web フロントエンド / Android アプリ (Java, Kotlin)
  • Java 9 と Kotlin 1.2 気になりますね!
    • 『supporting the Java 9 module system is one of the main priorities for Kotlin 1.2』 (Kotlin Support for Java 9 Module System?)
    • Kotlin 1.1 の標準ライブラリにおけるパッケージ移動 (kotlin.reflect)

Kotlin の活用

  • Android での Kotlin 導入
    • キッズリーでは Kotlin 1.0 リリース前から Kotlin を導入
    • 前職では Kotlin か Jack ツールチェイン (を見据えた Retrolambda) かで揺れてた
    • Kotlin は Android を意識している部分も多い
  • サーバーサイドで Spring Boot + Kotlin
  • 雑感 :
    • チェック例外がないので考え方を変える必要がある以外は最高
    • Java からの移行は非常に楽 (100% interoperable with Java)

今日は Kotlin でのお手製 DI

  • “Thin Cake” パターンについて
  • 今年 1 月に話した内容
  • Kotlin での疑似モジュールミックスイン合成
  • デリゲーションも便利だよ! という話
  • 実用的というより興味本位なので気軽に聞いてください

Dependency Injection (DI)

  • 依存オブジェクトを注入するパターン
  • Android アプリ開発でもよく使われる
  • 様々な手法
    • Guice のような DI コンテナ、Dagger のようなライブラリ
    • Cake パターンのようなパターン ← 今日はココ
    • コンストラクタで依存オブジェクトを注入 (手動で単純)

Cake パターン

Cake パターン

  • Scala におけるお手製 DI のパターン
  • Odersky 先生らの論文発祥
  • コンポーネント再利用のための 3 つの抽象化
    • 抽象型メンバー
    • 自分型アノテーション
    • モジュールミックスイン合成
(「Scalable Component Abstractions」 より)

モジュールの合成

  • Trait (実装を持てるインターフェイスぽいもの) をモジュールとして使う
  • モジュール
    • 何らかの機能 (オブジェクト) を提供
    • 別のモジュールのオブジェクトに依存
      • 依存先オブジェクトの詳細を知らない
  • モジュールの組み合わせ → オブジェクトグラフ

“Thin Cake” パターン

モジュール

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"
}
https://scalafiddle.io/sf/wXq8fOM/22

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"
}
http://try.kotlinlang.org/#/UserProjects/lhd18hmac9csqh32i9u5mjhmd7/6dh5fg7gqkp4tkkup3sp6vl9bh

が…… 駄目っ……!

Object 'FooBarBazComponent' must override public open val foo: String defined in FooModule because it inherits multiple interface methods of it
  • 継承先の複数のインターフェイスに同じシグネチャ
    • デフォルト実装はやっぱりデフォルト実装
  • 解決はできる : Kotlin でも Minimal Cake Pattern
  • そもそもデフォルト実装では値を保持できない問題

デリゲーションなら?

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"
    // ... (略) ...
  }
}
http://try.kotlinlang.org/#/UserProjects/lhd18hmac9csqh32i9u5mjhmd7/bhtihkpmr4gvil7ib0o8qcmu3v

オブジェクトグラフ

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) } }
  }
}
http://try.kotlinlang.org/#/UserProjects/lhd18hmac9csqh32i9u5mjhmd7/bhtihkpmr4gvil7ib0o8qcmu3v

疑似的に “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 {
    // ... 実装 ...
  }
}
http://try.kotlinlang.org/#/UserProjects/lhd18hmac9csqh32i9u5mjhmd7/95ntg0h5b3usm2qp0afp4ibpja

インジェクトされるオブジェクトのコンストラクタ

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) }
  }
}
http://try.kotlinlang.org/#/UserProjects/lhd18hmac9csqh32i9u5mjhmd7/95ntg0h5b3usm2qp0afp4ibpja

おわり