0

我有这种情况,我有一个超级抽象类,它使用 Kotlin 发出不同类型的事件sealed classes

这些事件建模如下。

sealed class BaseEvent {
    object ConnectionStarted : BaseEvent()
    object ConnectionStopped : BaseEvent()
}

sealed class LegacyEvent : BaseEvent() {
    object TextChanged : LegacyEvent()
    object TextCleared : LegacyEvent()
}

sealed class AdvancedEvent : BaseEvent() {
    object ButtonClick : AdvancedEvent()
    object ButtonLongClick : AdvancedEvent()
}

这是发出这些事件的类

abstract class BaseViewModel<E : BaseEvent> {

    private fun startConnection() {
        emit(BaseEvent.ConnectionStarted) // <-- Error
    }

    fun emit(event: E){
        //...
    }
}

class LegacyBaskan : BaseViewModel<LegacyEvent>() {
    fun textChanged() {
        emit(LegacyEvent.TextChanged) // <-- Works
    }
}

class AdvancedBaskan : BaseViewModel<AdvancedEvent>() {
    fun buttonClicked() {
        emit(AdvancedEvent.ButtonClick) // <-- Works
    }
}

在这里,它只适用于子类,我可以在它们LegacyEventAdvancedEvent它们的关联类中发出任何事件。但是,对于BaseBaskan该类,我不能从 中发出事件,BaseEvent尽管我声明泛型类型E必须扩展BaseEvent.

我需要每个子类都可以访问自己的事件以及超类事件,但不能访问其他子类的事件。

我怎样才能仍然从BaseEvent基类中发出事件,同时让每个类只能发出自己的事件?

4

3 回答 3

0

不确定您是否对为什么它不允许您从基类发出项目感到困惑。由于 E 可以是 BaseEvent 的任何子类型,如果您的类可以发出ConnectionStarted,那么任何时候它被声明为BaseViewModel<AnythingBesidesConnectionStarted>.

我能想到的唯一方法是同时拥有该emit功能的私有和公共版本。您可能需要更改课程中未显示的其他代码。如果有一些函数返回E,你将不得不改变它,让它返回BaseEvent

abstract class BaseViewModel<E : BaseEvent> {

    private fun startConnection() {
        emitInternal(BaseEvent.ConnectionStarted)
    }

    private fun emitInternal(event: BaseEvent) {
      //...
    }

    fun emit(event: E){
        emitInternal(event)
    }
}
于 2021-10-21T12:49:02.573 回答
0

看起来您需要逆变,可以使用in. 假设您的基类只有诸如emit使用类型E作为参数类型而不是返回类型的方法,那么:

abstract class BaseViewModel<in E : BaseEvent> {

请参阅https://kotlinlang.org/docs/generics.html#use-site-variance-type-projections

于 2021-10-21T13:15:49.133 回答
0

您不能发出BaseEvent.ConnectionStartedin BaseViewModel(以及其他事件),因为E尚未定义,因此类型系统无法确定您不会发出另一个破坏泛型类型不变性的子类型的事件。

只需添加一个重载的私有版本,它接受BaseEvent参数(您需要一些@JvmName注释以使其可编译为 JVM 目标):

abstract class BaseViewModel<E : BaseEvent> {
    private fun startConnection() {
        emit(BaseEvent.ConnectionStarted)
    }

    @JvmName("emitBaseEvent")
    private fun emit(event: BaseEvent) {
        //...
    }

    fun emit(event: E) {
        emit(event as BaseEvent)
    }
}
于 2021-10-21T12:51:48.470 回答