如何设计一个Java也可以实现的协程接口?


#1

最近在设计一个库上出现了一点儿小问题,废话不多说先举个例子吧,假设我有一个接口,里面有一个挂起函数:

interface Invoker {
    suspend fun invoke()
}

但是我希望这个接口也可以由Java语言的用户自行实现,但是不想让他们发现内部的协程细节。
最开始,我想到的是通过 @JvmSynthetic 将挂起函数隐藏,就像这样:

interface Invoker {
    @JvmSynthetic
    suspend fun invoke() = invoke0()

    /** Java实现的时候只会看到此方法 */
    fun invoke0()
}

但是这样的话却对 kotlin 的使用者不友好,因为他们无法第一时间发现有一个挂起函数需要实现,并且他们还需要额外再手动实现一下对他们来说没有价值的 invoke0 函数。

然后,我便思考,将Javakotlin 需要进行实现的接口分离一次,就像这样:

sealed interface RootInvoker {
    suspend fun invoke()
    fun invoke0()
}


/** For kotlin */
interface SuspendInvoker : RootInvoker {
    /** Kt需要实现的 */
    override suspend fun invoke()
    override fun invoke0() = runBlocking { invoke() }
}


/** For java */
interface BlockInvoker : RootInvoker {
    /** Java需要实现的 */
    override fun invoke0()
    override suspend fun invoke() = invoke0()
}

但是我总感觉这样依旧不够优雅,因为只要有分离就有可能造成使用者错误实现的问题。

各位老哥们有无更好的思路能够使得Java与Kotlin在实现一个可挂起函数的时候互不干扰且符合各自语言特性呢?
:smiley_cat:


#2

接口只给Java用,Kotlin不用接口直接用函数类型,然后在外层调用上做重载

interface Invoker {
    fun invoke()
}

private var _invoker: (suspend () -> Unit)? = null

fun setInvoker(invoker: suspend () -> Unit) {
    _invoker = invoker
}

fun setInvoker(invoker: Invoker) {
    _invoker = suspend { invoker.invoke() }
}


#3

唔。。这样子似乎不太行,因为首先,Invoker的实现数量是不确定的,其次 Invoker 实际上是不止一个 invoke 函数的,可能还会有多个其他需要重写的普通函数
不过感谢你为我提出建议 :kissing_cat:


#4

也可以把你的第二种做法改一下,去掉RootInvoker:

interface BlockingInvoker {
    fun invoke()
}

interface SuspendInvoker {
    suspend fun invoke()
}

fun BlockingInvoker.toSuspend(): SuspendInvoker {
    val self = this
    return object : SuspendInvoker {
        override suspend fun invoke() {
            self.invoke()
        }
    }
}


#5

但是这样的话,BlockingInvokerSuspendInvoker 之间就没有必然的关系了,我虽然希望java和kotlin的用户都能够自行实现 Invoker,但是为了库内的健壮性,我希望在真正使用 Invoker 的时候无关它到底是Java实现的或者kotlin实现的,而是作为一个统一的 Invoker
就好比第二个做法中,我只使用 RootInvoker,但是不关系它到底是 Blocking的 还是 Suspend

说实话,我现在开始感觉似乎已经没什么更好的解决方法了。。。:pensive:


#6

使用的时候统一只使用 SuspendInvoker就好, BlockingInvoker始终通过 toSuspend 转成 SuspendInvoker再用


#7

嗯。。这样讲的话,似乎确实是一种办法,我觉得可以尝试一下


京ICP备16022265号-2 Kotlin China 2017 - 2018