Обработка общих слушателей событийного автобуса в Котлине - PullRequest
3 голосов
/ 23 октября 2019

Моя цель - реализовать очень простую шину событий в Kotlin без каких-либо сторонних библиотек. Я сделал это с помощью приведенного ниже кода.

class EventListener<T>(
    val owner: Any,
    val event: Class<T>,
    val callback: (T) -> Unit
)

interface IEventBus {
    fun <T> subscribe(owner: Any, event: Class<T>, callback: (T) -> Unit)
    fun unsubscribe(owner: Any)
    fun <T> push(event: T)
}

class EventBus : IEventBus {
    private val _listeners = mutableListOf<EventListener<*>>()

    override fun <T> subscribe(owner: Any, event: Class<T>, callback: (T) -> Unit) {
        val listener = EventListener(owner, event, callback)
        _listeners.add(listener)
    }

    override fun unsubscribe(owner: Any) {
        _listeners.removeAll {
            it.owner == owner
        }
    }

    override fun <T> push(event: T) {
        _listeners.forEach { listener ->
            try {
                val l = listener as EventListener<T> // is always a success
                l.callback(event)                    // throws an exception if can't handle the event
            } catch (ex: Exception) { }
        }
    }
}

И тогда использование будет таким:

// register listener
bus.subscribe(this, String::class.java) {
    print(it)
}
// push an event (from somewhere else in the project)
bus.push("Hello world!")

Это работает и полностью пригодно для использования, однако я не удовлетворен этим. .. Приведение слушателя в качестве EventListener всегда будет возвращать что-то, а затем, если l.callback (событие) не сможет обработать тип события, оно выдаст исключение. Поэтому, если подписчики подписаны на большое количество пользователей, они будут генерировать много нежелательных исключений, которые будут просто игнорироваться.

Я бы предпочел сначала выполнить какую-то проверку, например:

if (listener is EventListener<T>)
    listener.callback(event)

Но я нашелвыходит, что JVM теряет информацию об универсальных типах после компиляции. Я также обнаружил, что его можно обойти, используя inline и reified , которые не могут быть использованы в методе, который исходит из интерфейса ...

Поэтому мой вопрос: знаете ли вы какой-нибудь более изящный способ справиться с такой общей проблемой?

1 Ответ

2 голосов
/ 23 октября 2019

Поскольку вы уже выставляете класс события (EventListener#event), вы можете использовать isInstance(), чтобы проверить, совместим ли класс с назначением для экземпляра вашего события.

Итак, вместо:

if (listener is EventListener<T>)
    listener.callback(event)

Вы можете сделать:

if (listener.event.isInstance(event)) {
    // The cast is safe since you checked if the event can be received by the listener.
    (listener as EventListener<T>).callback(event)
}

Примитивы

Если вы хотите поддержать T и примитивными типами, выможно изменить Class<T> на KClass<T> или проверить экземпляр вручную для каждого типа примитива (например, event is Int, event is Long).

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