Kotlin泛型 – 如何建立非平凡的关系?(期望为Nothing)

huangapple go评论60阅读模式
英文:

Kotlin generics - how to set up non-trivial relations? (expected Nothing)

问题

Here is the translated content:

我想创建一个用于处理应用内事件的简单API(将它们的信息发送到各种目标)。

我想要按类型进行处理,这样事件处理器就会知道将哪个事件发送到哪里,这将由事件的子类型决定。

这是一个示例:

interface AppEvent

interface Sender<T: AppEvent> {
    fun send(event: T)
    val acceptsAbility: KClass<T>
}


class Loggable: AppEvent, HasMessage

interface HasMessage { val message: String }


class LogSender : Sender<Loggable> {
    override fun send(event: Loggable) = Logger(...).info(event.message)
    override val acceptsAbility = Loggable::class
}

fun main() {
    val dispatcher = Dispatcher<AppEvent>()
    val sender: Sender<Loggable> = LogSender()
    dispatcher.addSender(sender)
}

然后,特定事件应该是各种 ...able 接口的混合体,这将决定哪些 Sender 会将它们发送出去。这将由调度程序类使用:如果给定事件与某个发送方的接口匹配,那么将其传递给该发送方。在Kotlin中,这需要在运行时检查。

class ImportErrorEvent : AppEvent, Loggable

请注意,我更喜欢 Loggable 不继承自 AppEvent,但这需要TypeScript的类型兼容性方式,只需具有正确的属性和函数组合就足以匹配类型要求。

因此,我尝试编写一个基本的调度程序,但遇到了类型泛型问题。

class Dispatcher <T: AppEvent> {
    val senders: MutableList<Sender<out T>> = mutableListOf()

    fun <TF: Sender<out T>> addSender(a: TF) { senders.add(a) }

    fun dispatch(event: T) {
        if (event is sender.acceptedAbility)
                senders
            .filter { it.acceptsAbility.isInstance(event) }
            .forEach { it.send(event as AppEvent)} // This type check needs to be relaxed.
    }
}

class Dispatcher2 {
    val senders: MutableList<Sender<out AppEvent>> = mutableListOf()

    fun <T: AppEvent, TS: Sender<out T>> addSender(a: TS) { senders.add(a) }

    fun dispatch(event: Any) {
        senders
            .filter { it.acceptsAbility.isInstance(event) }
            .forEach { it.send(event as AppEvent)} // This type check needs to be relaxed.
    }
}

这两者在 senders.forEach { it.send(event) } 中都有编译错误:

Type mismatch.
   Required: Nothing
   Found: Any

我在Java中使用了extendssuper实现了相同的想法。

使用泛型编写此想法的正确方式是什么?
是否有一些关于Kotlin泛型的扩展文档我可以深入研究?

编辑:

类型安全性打算在运行时部分实现:

  1. 调度程序会询问发送方它可以接收的类型(例如 Loggable),
  2. 检查事件是否实现了该类型,或者是否具有所有必需的特性,
  3. 如果匹配,则传递给发送方,可以是类型转换或重新包装。

我已更改上面的代码以包含类型检查。

因此,我正在寻找一种在仍然对调用代码施加一些约束的情况下放宽类型检查的方法。

英文:

I would like to create a simple API for processing in-app events (sending their info to various targets).

I would like to have that type-based, so that the event processor would know where to send which event would be determined by the sub-type of the event.

Here is an example:

interface AppEvent

interface Sender<T: AppEvent> {
    fun send(event: T)
    val acceptsAbility: KClass<T>
}


class Loggable: AppEvent, HasMessage

interface HasMessage { val message: String }


class LogSender : Sender<Loggable> {
    override fun send(event: Loggable) = Logger(...).info(event.message)
    override val acceptsAbility = Loggable::class
}

fun main() {
    val dispatcher = Dispatcher<AppEvent>()
    val sender: Sender<Loggable> = LogSender()
    dispatcher.addSender(sender)
}

A particular event should then be a mixture of various ...able interfaces, which would determine, which all Senders would send them. This would be used by the dispatcher class: If the given event matches the interface of some sender, then pass it to that sender. In Kotlin, this needs to be checked at runtime.

class ImportErrorEvent : AppEvent, Loggable

Note to that - I would prefer Loggable not to inherit from AppEvent, but that would need the TypeScript way of type compatibility, where just having the right mix of properties and functions is enough to match the type requirement.

So I attempted to code a basic dispatcher, but ran into type generics issues.

class Dispatcher <T: AppEvent> {
    val senders: MutableList<Sender<out T>> = mutableListOf()

    fun <TF: Sender<out T>> addSender(a: TF) { senders.add(a) }

    fun dispatch(event: T) {
        if (event is sender.acceptedAbility)
                senders
            .filter { it.acceptsAbility.isInstance(event) }
            .forEach { it.send(event as AppEvent)} // This type check needs to be relaxed.
    }
}

class Dispatcher2 {
    val senders: MutableList<Sender<out AppEvent>> = mutableListOf()

    fun <T: AppEvent, TS: Sender<out T>> addSender(a: TS) { senders.add(a) }

    fun dispatch(event: Any) {
        senders
            .filter { it.acceptsAbility.isInstance(event) }
            .forEach { it.send(event as AppEvent)} // This type check needs to be relaxed.
    }
}

Both of these have a compilation error in senders.forEach { it.send(event) }:

Type mismatch.
   Required: Nothing
   Found: Any

I have the same idea implemented in Java using extends and super.

What would be the right way to code this idea with generic?
Is there some extended documentation on Kotlin generics I could dive into?

Edit:

The type safety is intended to be partially done at runtime:

TheDispatcher would

  1. Ask the sender for the type it can consume (e.g. Loggable),
  2. Check if the event implements that, or if it has all the required traits,
  3. Pass it to the sender if matched, with either type-casting, or re-wrapping.

I have changed the code above to include the type check.

So I am looking for a way to relax the type checking while still imposing some constraints for the calling code.

答案1

得分: 1

Here is the translated code:

如果我理解你的意思你需要编写类似于以下内容的代码

    interface AppEvent
    
    interface Sender<in T : AppEvent> {
        fun send(event: T)
    }
    
    interface HasMessage {
        val message: String
    }
    
    class Loggable : AppEvent, HasMessage {
        override val message: String = ""
    }
    
    class Testable : AppEvent
    
    class LogSender : Sender<Loggable> {
        override fun send(event: Loggable) = Unit
    }
    
    fun main() {
        val station = Dispatcher()
        val feeder: Sender<Loggable> = LogSender()
        station.addSender(feeder)
        station.dispatch(Loggable()) // It will be send
        station.dispatch(Testable()) // It will not send
    }
    
    class Dispatcher {
        private val senders = mutableListOf<Sender<AppEvent>>()
    
        inline fun <reified T : AppEvent> addSender(sender: Sender<T>) {
            val wrapper: Sender<AppEvent> = object : Sender<AppEvent> {
                override fun send(event: AppEvent) {
                    if (T::class.isInstance(event)) {
                        sender.send(event as T)
                    }
                }
            }
            addCommonSender(wrapper)
        }
    
        fun addCommonSender(sender: Sender<AppEvent>) {
            senders.add(sender)
        }
    
        fun dispatch(event: AppEvent) {
            for (sender in senders) {
                sender.send(event)
            }
        }
    }

inline + reified 允许你捕获 T 的 sender 类型之后你可以创建一个实现 Sender<AppEvent> 的包装器并在运行时检查后将其转换为 T

最后你将拥有一个与类型无关的调度程序和强类型的发送器

另一种选项是强类型的调度程序

    class Dispatcher2<T: AppEvent> {
        val senders: MutableList<Sender<T>> = mutableListOf()
    
        fun addSender(a: Sender<T>) { senders.add(a) }
    
        fun dispatch(event: T) {
            for(sender in senders) {
                sender.send(event)
            }
        }
    }

它可以像这样使用

    fun main() {
        val station = Dispatcher2<Loggable>()
        val feeder: Sender<Loggable> = LogSender()
        station.addSender(feeder)
        station.dispatch(Loggable()) // Dispatched
        station.dispatch(ImportErrorEvent()) // Dispatched too
    }

Please note that I have removed the HTML encoding (e.g., &quot;) from the code as it's not necessary in Kotlin.

英文:

If I understand you, you have to write something like this:

interface AppEvent
interface Sender&lt;in T : AppEvent&gt; {
fun send(event: T)
}
interface HasMessage {
val message: String
}
class Loggable : AppEvent, HasMessage {
override val message: String = &quot;&quot;
}
class Testable : AppEvent
class LogSender : Sender&lt;Loggable&gt; {
override fun send(event: Loggable) = Unit
}
fun main() {
val station = Dispatcher()
val feeder: Sender&lt;Loggable&gt; = LogSender()
station.addSender(feeder)
station.dispatch(Loggable()) // It will be send
station.dispatch(Testable()) // It will not send
}
class Dispatcher {
private val senders = mutableListOf&lt;Sender&lt;AppEvent&gt;&gt;()
inline fun &lt;reified T : AppEvent&gt; addSender(sender: Sender&lt;T&gt;) {
val wrapper: Sender&lt;AppEvent&gt; = object : Sender&lt;AppEvent&gt; {
override fun send(event: AppEvent) {
if (T::class.isInstance(event)) {
sender.send(event as T)
}
}
}
addCommonSender(wrapper)
}
fun addCommonSender(sender: Sender&lt;AppEvent&gt;) {
senders.add(sender)
}
fun dispatch(event: AppEvent) {
for (sender in senders) {
sender.send(event)
}
}
}

inline + reified allows you to capture sender's type of T. After it you can create wrapper which will implements Sender<AppEvent> and cast it to T after runtime check.

Finally you have type independent dispatcher and strongly typed senders.

The other option it's strongly typed dispatcher:

class Dispatcher2 &lt;T: AppEvent&gt; {
val senders: MutableList&lt;Sender&lt;T&gt;&gt; = mutableListOf()
fun addSender(a: Sender&lt;T&gt;) { senders.add(a) }
fun dispatch(event: T) {
for(sender in senders) {
sender.send(event)
}
}
}

It could be used like this:

fun main() {
val station = Dispatcher2&lt;Loggable&gt;()
val feeder: Sender&lt;Loggable&gt; = LogSender()
station.addSender(feeder)
station.dispatch(Loggable()) // Dispatched
station.dispatch(ImportErrorEvent()) // Dispatched too
}

huangapple
  • 本文由 发表于 2023年6月12日 04:31:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76452410.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定