inline class を知った
inline class UserId(val value: String)
Kotlin 1.4.30 のリリース記事を読んで、inline class
を知ったのでドキュメントを読んでみました。
個人的にかなり有用に感じたのは、typealias
と違ってassignment-compatible
(代入互換性)がない点です。
個人開発アプリでtypealias
を使っているところがあるので、そこを置き換えつつ、その有用性を体感してもらえたらと思います。
実行環境
- Kotlin バージョン 1.4.30
修正前のコード
それぞれtypealias
でRoomId
とUserId
を定義しています。
どちらもString型であることには変わりませんが、data class
などに列挙したときに役割がパッと見やすくなります。
しかしパッと見やすくなるだけで、Value Object として機能していないので注意です。
typealias RoomId = String typealias UserId = String data class Room( val id: RoomId, val ownerId: UserId, ... )
typealiasには代入互換性がある
以下のように、RoomRepositoryを実装しました。
roomId
を引数にとって、指定したRoom
のデータを取得するような実装のつもりです。
interface RoomRepository { suspend fun get(roomId: RoomId): Room }
以下は、呼び出し元の実装例です。
実装例①
問題なく動作します。
fun getRoom(roomId: RoomId, userId: UserId) { viewModelScope.launch { val room = roomRepository.get(roomId) } }
実装例②
実装例①と違ってroomRepository.get(...)
の引数にuserId
を渡していますが、問題なく動作します(!?)。
roomId
とuserId
は別の型であるかのように見えますが、実際はどちらもString型なので代入ができてしまいます。
このままだと意図しない代入が起きてしまいます。
fun getRoom(roomId: RoomId, userId: UserId) { viewModelScope.launch { val room = roomRepository.get(userId) } }
修正後のコード
inline class
のUserId
とRoomId
を作成します。
UserId
とRoomId
の型を使っているところに変更はありません。
これによって、実装例②のコードはエラーになります。
inline class UserId(val value: String) inline class RoomId(val value: String) data class Room( val id: RoomId, val ownerId: UserId, ... )
まとめ
typealias
は代入互換性があるので、意図しない代入ができてしまいます。
一方で、inline class
は代入互換性がありません。これによって意図しない代入を防ぐことができ、Value Object として扱うことができます。
「typealias
のままでも注意していればよくない?」という方もいるかと思いますが、言語の仕組みを利用してヒューマンエラーが起こらないようにした方がより不具合がない開発ができると思います。