【问题标题】:Sealed classes generics密封类泛型
【发布时间】:2021-10-21 08:15:36
【问题描述】:

我有一个场景,我有一个超级抽象类,它使用 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 发出事件,同时让每个类只能发出自己的事件?

【问题讨论】:

    标签: kotlin generics sealed-class


    【解决方案1】:

    不确定您是否对为什么它不允许您从基类发出项目感到困惑。由于 E 可以是 BaseEvent 的任何子类型,如果您的类可以发出 ConnectionStarted,那么任何时候将其声明为 BaseViewModel&lt;AnythingBesidesConnectionStarted&gt; 都会违反其约定。

    我能想到的唯一方法是同时拥有 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)
        }
    }
    

    【讨论】:

      【解决方案2】:

      你不能在BaseViewModel(以及其他事件)中发出BaseEvent.ConnectionStarted,因为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)
          }
      }
      

      【讨论】:

        【解决方案3】:

        看来您需要逆变,可以使用in 来实现。假设您的基类只有 emit 之类的方法,它们使用类型 E 作为参数类型,而不是作为返回类型,那么:

        abstract class BaseViewModel<in E : BaseEvent> {
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多