Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Delegatesと拡張関数・拡張プロパティその合わせ技

 Delegatesと拡張関数・拡張プロパティその合わせ技

2021/09/22の Server-Side Kotlin Study #2 にて発表した、KotlinのDelegatesと拡張関数・拡張プロパティの組み合わせに関する資料です。

https://server-sider-kotlin.connpass.com/event/224077/

Hideyuki Takeuchi

September 22, 2021
Tweet

More Decks by Hideyuki Takeuchi

Other Decks in Technology

Transcript

  1. 株式会社イエソド 代表取締役 エンジニア • 静的型付け言語以外でのプロダクト開発を認めない系ITエンジニア • JVM以外信じない系ITエンジニア • パソコンの大先生 •

    物理レイヤーからフロントエンドまで • 全業種・全職種の人とだいたい話が合わせられる • 元 株式会社ユーザベース チーフテクノロジスト 「SPEEDA」「NewsPicks」「FORCAS」を作った 東証の鐘を鳴らしたことがある たけうち ひでゆき @chimerast きれいなたけうちさん マサカリを投げるたけうちさん
  2. 言語 Kotlin / TypeScript フロントエンド HTML / CSS / Sass(SCSS)

    / TypeScript / Vue.js / Vuex バックエンド Ktor / Exposed / Express データベース PostgreSQL (Cloud SQL) インフラ GCP / Docker / Kubernetes / Istio バージョン管理等 GitHub / JIRA CI/CD CloudBuild / GitHub Actions / Kustomize / Argo CD コミュニケーション Slack / Notion / Zoom / Google Meet / Miro / Figma 技術スタック
  3. "Open Classes" について • いにしえから存在する、 
 「俺、このクラス or インターフェースに横から機能を追加・変更したいんだよね」 


    というプログラマのエゴを満たすための機能 • "Extension Classes" とも言う • 情報科学の世界では古くからの研究分野 (論文いっぱいある)
  4. "Open Classes" 全般について • あんま使うな • ライブラリ開発者の設計思想を破壊する可能性がある • モジュラリティを破壊する可能性がある •

    でもかゆいところに手が届いて便利だよね • ライブラリ開発者が想定しきれなかった所をモンキーパッチ • 変換関数は、拡張関数使った方が収まりが良いよね
  5. Kotlinでの"Open Classes" • 拡張関数・拡張プロパティ • 元のクラスの挙動を変更することはなく機能の追加のみ • スコープの概念がある ← 結構重要

    • ほどよくできることが制限されているので、ちょうどよい案配 • 完全な無法地帯にはならない ※ 個人の感想です
  6. 拡張関数 fun ByteArray.uuidv5(namespace: UUID): UUID { return UUIDv5(namespace, this) }

    fun <拡張したいクラス>.<新しく追加したい関数名>(<引数>): <返り値の型> { // 拡張したクラスのインスタンスが this としてアクセスできるので // 何かしらの処理をして返す } 書き方 実際の例
  7. 拡張プロパティ val編 val <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this

    としてアクセスできるので // インスタンスの状態を返す } 書き方 実際の例 val String.uuid get() = when (this.length) { 22 -> UUIDConverter.decodeBase64(this) 36 -> requireNotNull(UUID.fromString(this)) else -> throw IllegalUUIDFormatException(this) }
  8. 拡張プロパティ var編 var <拡張したいクラス>.<新しく追加したいプロパティ名> get(): <プロパティの型> { // 拡張したクラスのインスタンスが this

    としてアクセスできるので // インスタンスの状態を返す } set(value: <プロパティの型>){ // 拡張したクラスのインスタンスが this としてアクセスできるので // インスタンスの状態を変更する } 書き方
  9. Delegates Delegates って? val lazyValue: String by lazy { "Hello"

    } 上のようなフィールド定義の lazy関数 が Delegateインスタンスを返す プロパティのsetterやgetterで行う処理を共通化できる よくある用途としては、 lazy (遅延初期化)、 Delegates.observable() (監視) val <プロパティ名>: <プロパティの型> by <Delegateインスタンス> Delegatesを使ったプロパティ定義の構造
  10. Delegates の作り方 Delegates の定義のしかた class <Delegateのクラス名> { operator fun getValue(thisRef:

    Any?, property: KProperty<*>): String { // プロパティのgetterの代わりに実行される処理 } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { // プロパティのsetterの代わりに実行される処理 } }
  11. Exposed がちょっとつらかった部分 課題編 1. Exposed のデータソースとして HikariCP を利用 2. 開発時にコードの更新をして

    Ktor の Auto-reload が走ると、 Exposed と HikariCP は再作成されるが、 更新前の HikariCP のコネクションプールは破棄 されず残ったまま 3. 何度かコードを更新すると、ローカルのDB側の最大受け入れコネクション数を 超えてしまい、JVMごと再起動する必要が出てくる
  12. Exposed がちょっとつらかった部分 解決できなかった編 1. Ktor 側には、 Auto-reload が走ったときに、環境の破棄をフックする為の environment.onShutdown() というメソッドが用意されていた

    2. Exposed 側は、コネクションを管理する Database インスタンスのクローズ処 理をする方法はあったが、紐付く HikariCP を破棄するためのフック等が全く用 意されていなかった。 3. そうだ、拡張関数でDatabase クラスに HikariCP を閉じるためのメソッドを追 加すればいいじゃないか。 → 失敗
  13. 拡張プロパティとDelegatesでなんとかする fun connect(): Database { val hikariDataSource = HikariDataSource() return

    Database.connect(hikariDataSource).apply { dataSource = hikariDataSource } } private var Database.dataSource: HikariDataSource by ExtensionInstanceVariable() fun Database.shutdown() { this.dataSource.close() TransactionManager.closeAndUnregister(this) } Database.dataSource プロパティへの操作を 
 ExtensionInstanceVariable() に移譲
  14. 拡張プロパティの値を保持するDelegate 無理やり作られた解決策 private class ExtensionInstanceVariable<T : Any> : ReadWriteProperty<Any, T>

    { private val valueMap: MutableMap<Any, T> = Collections.synchronizedMap(WeakHashMap()) override fun getValue(thisRef: Any, property: KProperty<*>): T { return valueMap[thisRef] ?: throw IllegalStateException("Property ${property.name} should } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { valueMap[thisRef] = value } } WeakHashMap で thisRef に紐付けて値を保持 速度的な性能は保証できない
  15. ダメな拡張プロパティのためのDelegates private class BadExtensionInstanceVariable<T : Any> : ReadWriteProperty<Any, T> {

    private var value: T? = null override fun getValue(thisRef: Any, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initial } override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { this.value = value } } 拡張プロパティへのDelegateは、インスタンス毎ではなく、 一つの object に保持されるため、全てのインスタンスで 
 同じ値が返るようになるし上書きされる