alakida
alakida

Reputation: 48

Kotlin - Is there a way of defining an explicit scope of a function with receiver defined in companion object?

Imagine you have a following code:

interface SomeClient {

    fun isValid(value: String): Boolean
}

class SomeData private constructor(
    val value: String
) {

    companion object {

        fun SomeClient.create(value: String): SomeData? =
            if (isValid(value)) {
                SomeData(value)
            } else {
                null
            }
    }
}

class SomeOtherData private constructor(
    val otherValue: String
) {

    companion object {

        fun SomeClient.create(value: String): SomeOtherData? =
            if (isValid(value)) {
                SomeOtherData(value)
            } else {
                null
            }
    }
}

class SomeUseCase(
    someClient: SomeClient
) : SomeClient by someClient {

    fun run() {
        val someData = SomeData.create("hello")
        val someOtherData = SomeOtherData.create("wassup")
    }
}

The whole intent is to provide a static factory method to create a valid value objects (SomeData, SomeOtherData) but the validation logic includes some IO operation. So I want to limit the scope of where the create methods may be called to classes that implement SomeClient.

The problem here is that the compiler can't resolve the companion object methods (SomeData#create, SomeOtherData#create) inside of the SomeUseCase#run, it complains that the receiver is incorrect.

Of course I could do it like that

class SomeUseCase {
    fun run() {
        val someData = this.createSomeData(...)
        val someOtherData = this.createSomeOtherData(...)
    }
}

and rename the creation methods accordingly, but I want to keep the names so it would be SomeData.create, not SomeData.createSomeData.

Is there a way to achieve this? Does it make sense at all?

Upvotes: 1

Views: 186

Answers (1)

When you write fun SomeClient.create(value: String) in SomeData companion object you are not defining create method for companion object, but an extension function for SomeClientin the scope of companion object.

If you rewrite it so that it become a method of companion object, you will see that there is no receiver for isValid() call, so it need to be passed there as a parameter:

class SomeData private constructor(val value: String) {
    companion object {
        fun create(value: String, validator: SomeClient): SomeData? =
            if (validator.isValid(value)) SomeData(value) else null
    }
}

After that it may be called in the scope of SomeClient like that:

val someData = SomeData.create("hello", this)

It's a bit cumbersome to repeat this for each creation call, so you may define an extension function for SomeData companion object in the scope of SomeClient interface:

interface SomeClient {
    fun isValid(value: String): Boolean

    fun SomeData.Companion.create(value: String) = create(value, this@SomeClient)
}

After that it may be called in the scope of SomeClient in a desired way:

val someData = SomeData.create("hello")

Upvotes: 2

Related Questions