🌟

Kotlinの委譲クラスでドメイン固有コレクション型を3分クッキングしよう

2024/08/28に公開

従来的な方法論

ファーストクラスコレクション
ThoughtWorksアンソロジーにて紹介された実装パターンです。

一つのコレクションをラップしたドメイン固有型を作るパターンです。

example.kt
class BulkBuyingOrder(private val list: List<Item>) {
    init {
        require(original.size > 5)
    }
}

上記のようなドメイン固有型を用意し関数引数の型をそれとすることで事前条件の担保された引数を関数に渡すことができるようになります。関数の全域性を型(静的)レベルで検証・保証することができ、関数側での不要なハンドリングが不要になりシンプルになります。

※全域関数については以下の記事等を参考にしてみてください
https://zenn.dev/loglass/articles/76674f0eddfa8a

弊害

リストを内部に保持しているために、内部のリストに対する操作を行いたい場合専用の関数を用意する or 内部のリストを外部から取り出せるようにする必要性があります。
前者は標準APIを利用したいだけのようなケースでは冗長感が出てきます。
後者はせっかくドメイン固有型を作ったのに中身をお手軽に差し出してしまってはドメイン固有型の価値が暴落しそうです。

3分クッキング

Kotlinには委譲クラスという便利な言語仕様が用意されています。

https://kotlinlang.org/docs/delegation.html

こちらを使うことで、ドメイン固有なコレクション型を3分で作れます。

example.kt
class BulkBuyingOrder (private val original: List<Item>) : List<Item> by original {
    init {
        require(original.size > 5)
    }
}

fun discount(order: BulkBuyingOrder): OrderPrice {
    return OrderPrice(order.sumOf { it.price.value } - bulkBuyingDiscountAmount)
}

BulkBuyingOrder自身をItemのListと同じように扱えることが大きなメリットです。
内部のリストそのものはprivateなプロパティで取り出してList<Item>型として利用される可能性をなくせる点が非常にありがたい点だと思っています。

まとめ

強力な言語機構のおかげで、便利さを損なわないドメイン固有コレクション型を作り出すことができるので非常に便利です。
ドメイン固有型を作るハードルが下がるおかげで、関数のシグネチャーレベルで関数の仕様(事前条件)を明示的にしていくハードルが下がり、明確で安全なコードを書いていく文化を後押ししてくれる可能性が十分にあると思います。

Discussion