Kotlin вывод типа на "предположительно" правильных типах - PullRequest
1 голос
/ 13 июля 2020

Я новичок в Kotlin, и я играл с ним. Я очень хотел создать довольно простую шину событий c. Итак, я придумал это

interface Event
interface EventListener<E : Event> {
    fun handle(event: E)
}

interface EventBus {
    fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>)
}

class MyBus() : EventBus {
    private val eventListeners: MutableMap<String, MutableList<EventListener<out Event>>> = mutableMapOf()

    constructor(listeners: List<Pair<Class<Event>, EventListener<Event>>>) : this() {
        listeners.forEach {
            registerListener(it.first, it.second)
        }
    }

    override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
        val key = aClass.name
        val listeners: MutableList<EventListener<out Event>> = eventListeners.getOrPut(key) { mutableListOf() }
        listeners.add(eventListener)
    }
}


val bus = MyBus(
    listOf(
        MyEvent::class.java to MyEventListener()
    )
)

class MyEvent : Event
class AnotherEvent : Event
class MyEventListener : EventListener<MyEvent> {
    override fun handle(event: MyEvent) {
    }
}

. Когда я пытаюсь создать MyBus, используя конструктор, принимающий список пар, я получаю

Type inference failed. Expected type mismatch: inferred type is List<Pair<Class<MyEvent>,MyEventListener>> but List<Pair<Class<Event>,EventListener<Event>>> was expected

Но если я изменю конструктор должен быть чем-то вроде

constructor(listeners: List<Pair<Class<out Event>, EventListener<out Event>>>) : this() {
        listeners.forEach {
            registerListener(it.first, it.second)
        }
    }

добавление почти везде, тогда конструктор MyBus работает, но вызов registerListener (..) прерывается по той же самой причине, что и раньше. Так что единственный способ решить эту проблему - добавить "out" также в функцию registerListener.

Я подозреваю, что делаю здесь что-то не так, но не знаю, что именно. Любая помощь?

1 Ответ

4 голосов
/ 13 июля 2020

Если вы хотите, чтобы ваш EventListener мог потреблять Event s, тогда его тип должен быть инвариантным или ковариантным (не объявлен out). Если он позволит вам передать ваш EventListener<MyEvent>, как если бы это был EventListener<Event>, тогда ваш MyBus класс может вызвать listener.handle(event) с некоторым Event, который не MyEvent, например AnotherEvent. Тогда вы получите ClassCastException, когда он попытается преобразовать этот AnotherEvent в MyEvent.

Чтобы иметь возможность хранить различные типы инвариантных обработчиков событий, вам нужно будет удалить ограничения дисперсии, используя звездочку проекцию, и примените их, когда получите их с карты. Так что превратите ключи карты в объекты класса, а не просто в строки. Поскольку при работе со звездообразными типами у вас не будет помощи компилятора, вам нужно быть осторожным, чтобы вы добавляли в MutableMap только элемент того же типа, что и связанный с ним ключ класса. Затем, когда вы извлекаете элементы, приводите только к инвариантному типу.

Другая часть вашей проблемы заключается в том, что вашему конструктору нужен общий c тип. Сейчас он работает исключительно с Event, поэтому не может обрабатывать подтипы Event. Kotlin (пока?) Не поддерживает общие типы c для конструкторов, поэтому вам придется делать это с помощью фабричной функции.

Вот пример всего вышеперечисленного.

class MyBus() : EventBus {
    private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()

    override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
        val listeners = retrieveListeners(aClass)
        listeners.add(eventListener)
    }

    private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
        @Suppress("UNCHECKED_CAST")
        return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
    }
}

// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
    return MyBus().apply {
        listeners.forEach {
            registerListener(it.first, it.second)
        }
    }
}

И вы можете изменить тип заводского параметра с <List>Pair на vararg Pair, чтобы его было проще использовать.

Вот урезанный пример, объясняющий ограничение отклонения.

Ваш интерфейс для потребителя событий:

interface EventListener<E : Event> {
    fun handle(event: E)
}

Две реализации Event:

class HelloEvent: Event {
   fun sayHello() = println("Hello world")
}

class BoringEvent: Event {}

Класс, реализующий интерфейс:

class HelloEventListener: EventListener<HelloEvent> {
    override fun handle(event: HelloEvent) {
        event.sayHello()
    }
}

Теперь у вас есть EventListener, который может обрабатывать только HelloEvent s. Попробуйте относиться к нему как к EventListener<Event>:

val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!

Представьте, что компилятор не помешал вам сделать это, и вы делаете следующее:

val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!

Если бы это было разрешено, ваш HelloEventListener попробуйте вызвать sayHello() в BoringEvent, у которого нет этой функции, поэтому он взломает sh. Это то, от чего вас могут защитить дженерики.

Теперь предположим, что ваш HelloEventListener.handle() не звонил event.sayHello(). Что ж, тогда он мог бы безопасно обработать BoringEvent. Но компилятор не делает за вас такой анализ. Он просто знает, что вы объявили, что HelloEventListener не может обрабатывать ничего, кроме HelloEvent.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...