【问题标题】:Generics: Abstract class and type of child泛型:抽象类和子类型
【发布时间】:2015-12-30 08:50:29
【问题描述】:

我有一个名为 Presenter 的抽象类:

abstract class Presenter<V> {

    fun bind(view: V) {
        ...
    }

    ...
}

我有这些演示者的实现:

class FolderChooserPresenter : Presenter<FolderChooserView>() {
    ...
}

并查看在指定点调用绑定方法的类:

class FolderChooserActivity : BaseView(), FolderChooserView {

    @Inject lateinit var presenter: FolderChooserPresenter

    // method of baseview
    override fun onStart() {
        super.onStart()

        presenter.bind(this)
    }
}

我想要归档的是为像FolderChooserActivity 这样的类提供一个基类,它会自动调用绑定方法。
在所有实现中一遍又一遍地重复这些调用感觉很愚蠢。

我的方法是有一个抽象类,它扩展了调用绑定方法的BaseView。但这显然不起作用,因为绑定类需要实现而不是抽象类。

【问题讨论】:

  • FolderChooserView 是一个接口,所有视图接口都有一个共同的祖先接口吗?
  • 添加 android 标签可能会有所帮助,如果这是 Android 类层次结构中的常见问题,其他人可能已经解决了它。如果使用 Java 泛型解决,可能与 Kotlin 的解决方案相同。您可能还想使用java 标记询问它,然后编辑问题以针对 Java 或 Kotlin 说明如何使用泛型解决此问题,同时避免可能的运行时未经检查的强制转换。它不需要局限于 Kotlin。
  • 是的,它是一个接口。不,他们没有共同的祖先接口。但是,如果这样可以解决问题并绕过强制转换,那是一种选择。
  • 它实际上并没有解决问题,你会从祖先转换到后代,这仍然需要未经检查的转换。请参阅下面的答案,我试过了。

标签: generics kotlin


【解决方案1】:

您可以向BaseView 类添加两个通用参数,并将this 转换为V

open class Presenter<V> {
    fun bind(v: V) {}
}

open class BaseView<P, V> where P : Presenter<V> {
  lateinit var presenter: P

  fun onStart() {
    p.bind(this as V)
  }
}

你会像这样使用它

class FolderPresenter : Presenter<FolderChooserView>() {

}

class FolderChooserView : BaseView<FolderPresenter, FolderChooserView>()

不幸的是,如果您混淆了参数,您将无法从编译器获得任何帮助,因为未经检查的强制转换:

class SomeOtherView : BaseView<SomeOtherPresenter, FolderChooserView>()

【讨论】:

  • 嗯,我不喜欢这个。我正在考虑将其设计为通用库,因此我真的想避免这些未经检查的强制转换。我想要实现的目标没有解决方案吗?
  • 我也处理过这个问题,并且有一个适当的解决方案,结果证明这很复杂。请参阅 PresenterContainer 和基础 RelativeLayoutContainer(在 java 中)。
  • 这些是我的旧版本库的文件,其中 Presenter 和 View 的通用类型相互引用以强制执行正确的行为。不幸的是this requires a lot of sort of complex boilerplate
  • 这个演员阵容并不是一件坏事,如果有的话,没有很多好的答案。你总是可以添加一个断言来给出一个非常直接的错误消息,因为这些会在创建活动时立即发生,你会在测试的早期看到错误。
  • 是的,这就是我选择这样做的原因。
【解决方案2】:

@nhaarman 的答案很接近,但如果被绑定的类实际上不是视图的类型,则会留下漏洞。

这个演员阵容并不是一件坏事,如果有的话,也没有很多好的答案。你总是可以添加一个断言来给出一个非常直接的错误消息,因为这些会在创建活动时立即发生,你会在测试的早期看到错误

如果不创建将关系的所有部分作为一个整体来管理的东西,我认为您不会轻易获得更好的答案。我认为他的回答风险很小。

编译时检查以避免运行时错误

你可以编写一个函数,在编译时检查缺失的部分,人们可以像编译时断言一样使用它。

// a function that is used when people want to check validity at compile time,
// it does nothing but cause compiler error if wrong heirarchy

fun <A: V, P: Presenter<V>, V : View> checkValid() {
    // empty on purpose, used for compile time check
}

// successful:
checkValid<FolderChooserActivity, FolderPresenter, FolderChooserView>()

// error below: "Kotlin: Type argument is not within its bounds: should be subtype of 'test.so.FolderChooserView'"
checkValid<TryingToFoolItActivity, FolderPresenter, FolderChooserView>()

// this is blocked because SomeOtherActivity has its own compiler error so SomeOtherActivity type is not fully known
checkValid<SomeOtherActivity, FolderPresenter, FolderChooserView>()

使用函数创建类,并带有编译时检查以避免运行时错误

您还可以要求使用函数来构造*Activity 类。但是如果你不能强制人们确保他们拥有正确的视图基类,你就不能强制使用这个函数。无论如何,只是为了给这个问题更多的想法。

inline fun <reified A: V, P: Presenter<V>, V : View> makeActivity(): A {
    return A::class.java.newInstance() 
}

// successful:
val good1 = makeActivity<FolderChooserActivity, FolderPresenter, FolderChooserView>()

// error below: "Kotlin: Type argument is not within its bounds: should be subtype of 'test.so.FolderChooserView'"
val bad1 = makeActivity<TryingToFoolItActivity, FolderPresenter, FolderChooserView>()

完整代码:

我将完整的代码放在这里,以便我可以对此进行更多探索并尝试获得替代的完整答案。这实际上只是@nhaarman 答案的一种变体。

// sample classes

class FolderPresenter : Presenter<FolderChooserView>() { }

class BadPresenter : Presenter<RandomView>() { }

// successful declaration
class FolderChooserActivity : BaseActivity<FolderPresenter, FolderChooserView>(), FolderChooserView { }

// Error: "Kotlin: Type argument is not within its bounds: should be subtype of 'test.so.Presenter<test.renlearn.solrpref.FolderChooserView>'"
class SomeOtherActivity : BaseActivity<BadPresenter, FolderChooserView>(), FolderChooserView {}

// Runtime error, we are not a FolderChooserView
class TryingToFoolItActivity : BaseActivity<FolderPresenter, FolderChooserView>() {}

// now the version using a function to construct the activity, where
// this function adds the missing step of compiler time validation.

inline fun <reified A: V, P: Presenter<V>, V : View> makeActivity(): A {
    return A::class.java.newInstance()
}

// or a function that is used when people want to check validity at compile time,
// it does nothing but cause compiler error if wrong heirarchy

fun <A: V, P: Presenter<V>, V : View> checkValid() {
    // empty on purpose, used for compile time check
}

public fun foo() {
    // successful:
    val good1 = makeActivity<FolderChooserActivity, FolderPresenter, FolderChooserView>()

    // error below: "Kotlin: Type argument is not within its bounds: should be subtype of 'test.so.FolderChooserView'"
    val bad1 = makeActivity<TryingToFoolItActivity, FolderPresenter, FolderChooserView>()

    // this is blocked because SomeOtherActivity has its own compiler error so SomeOtherActivity type is not fully known
    val bad2 = makeActivity<SomeOtherActivity, FolderPresenter, FolderChooserView>()

    // successful:
    checkValid<FolderChooserActivity, FolderPresenter, FolderChooserView>()

    // error below: "Kotlin: Type argument is not within its bounds: should be subtype of 'test.so.FolderChooserView'"
    checkValid<TryingToFoolItActivity, FolderPresenter, FolderChooserView>()

    // this is blocked because SomeOtherActivity has its own compiler error so SomeOtherActivity type is not fully known
    checkValid<SomeOtherActivity, FolderPresenter, FolderChooserView>()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-15
    • 2010-11-08
    • 2012-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多