/var/log/jsoizo

メモ帳 技術とか趣味とか

コンストラクタの返却する型を変えたいときのcompanion object + invoke()パターン

こういうクラスがあり、newする時にエラー型を明示して Either<IllegalArgumentException, Email> が返却されるようにしたいとする。

class Email(val address: String) {
    init {
        require(address.contains("@")) { "Invalid email address" }
    }
}

とすると、このように書くことが多いのだが、呼び出しごとに Email.of("foo@bar.com") みたいに書く必要があるのがやや面倒だと感じていた。

class Email private constructor(val address: String) {
    companion object {
        fun of(address: String): Either<IllegalArgumentException, Email> = if (address.contains("@")) {
            Email(address).right()
        } else {
            (IllegalArgumentException("Invalid email address")).left()
        }
    }
}

of をこのようにすると引き続き Email("foo@bar.com") でEmailのインスタンスが作成できるようになる。
また、constructorと違ってcontext receiverやアノテーションもつけられるという点も良い。

operator fun invoke(address: String)): Either<IllegalArgumentException, Email>

ただinvokeに関して、IDE上でジャンプする時に直接Invokeには飛んでくれなくて、invokeが実装されているクラス今回だとcompanion objectに飛んでしまう。これは コンパイラがEmail()をコンパイル時にEmail.companion.invoke()に変換する と振る舞うことをイメージできないとコードの追いづらさが上がるところがあり良し悪しあるなとは感じる。そのあたりを理解して使ったほうが良いかもしれない。

個人レベルでは良いが現場では使いづらいテクニック。

参考

kotlinlang.org