B255
B255

Reputation: 307

How to reference an instance method in constructor invocation

I am writing some coroutines based event handling code in Kotlin and it's going great. I have code in various events handlers that does the same thing and I am trying to put this code in one place. I am stuck on the following. The idea is that subclasses can specify which event types the can handle by providing a map of classes to methods. I can't get it to compile though. Is there a way to make this work? Is there a better way of doing it? Thanks.


abstract class EventHandler(private val handlers: Map<KClass<out Event>, suspend (Event) -> Unit>) {
    suspend fun handle(event: Event) {
        val handler = handlers[event::class]
        if (handler != null) {
            handler(event)
        } else {
            throw IllegalStateException("No handler configured for $event")
        }
    }
}

data class ExampleEvent(private val id: String): Event

class ExampleHandler(): EventHandler(mapOf(ExampleEvent::class to handleExample)) {
                                                                  ^^^^^^^^^^^^^ - compile error
    suspend fun handleExample(event: ExampleEvent) {
        TODO()
    }
}

Upvotes: 1

Views: 159

Answers (1)

Giorgio Antonioli
Giorgio Antonioli

Reputation: 16214

You can't get it to compile for 3 different reasons:

  1. Since handleExample is an instance method, you can't refer it in the super constructor since the instance of your subclass isn't created yet.

  2. If you want a function reference of an instance method you should prefix it with ::, so in your case ::handleExample.

  3. The function handleExample accepts an event of type ExampleEvent so it doesn't conform to the input type Event, in this case, you'd need a cast.

That said, there's a solution to your problem which solves the 3 points above and the burden to repeat that boilerplate for each EventHandler subclass.

The explanation is all on the comments.

inline fun <reified T : Event> typedCoroutine(crossinline block: suspend (T) -> Unit): Pair<KClass<out Event>, suspend (Event) -> Unit> =
    // Since the type is reified, we can access its class.
    // The suspend function calls the required suspend function casting its input.
    // The pair between the two is returned.
    T::class to { event -> block(event as T) }

abstract class EventHandler {
    // Require the subclasses to implement the handlers.
    protected abstract val handlers: Map<KClass<out Event>, suspend (Event) -> Unit>

    suspend fun handle(event: Event) {
        val handler = handlers[event::class]
        if (handler != null) {
            handler(event)
        } else {
            throw IllegalStateException("No handler configured for $event")
        }
    }
}

class ExampleHandler : EventHandler() {
    // The type of typedCoroutine is inferred from handleExample.
    override val handlers: Map<KClass<out Event>, suspend (Event) -> Unit> = mapOf(typedCoroutine(::handleExample))

    suspend fun handleExample(event: ExampleEvent) {
        TODO()
    }
}

Using typedCoroutine you can fill the handlers map easily in all your EventHandler subclasses. 

Upvotes: 3

Related Questions