Kotlin:如何将具有不同参数的函数作为参数传递给其他函数

4
所以,我正在重新编写我的应用程序代码以使其“清晰”(分层,遵循Android团队推荐的MVVM模式)。
这里我有一个简单的Retrofit接口来与我的API通信。
interface Api {

    @GET("comments")
    suspend fun getPlaceComments(@Query("placeId") placeId: String): Response<List<CommentResponse>>

    @POST("comments")
    suspend fun addPlaceComment(@Header("placeId") placeId: String, @Header("text") text: String): Response<Unit>

    @DELETE("comments")
    suspend fun deletePlaceComment(@Header("placeId") placeId: String): Response<Unit>
}

只是一个简单的CRUD。

现在,再往上一层,我有我的SocialRepository。为避免重复代码,我创建了泛型方法callSafely,它接受一个暂停的API函数和一个placeId作为参数。

class SocialRepository {
    private val client: Api = ApiClient.webservice

    private suspend fun <T> callSafely(
        apiMethod: suspend (placeId: String) -> Response<T>,
        placeId: String,
    ): T? {
        Log.d(TAG, "$apiMethod called safely")

        var response: Response<T>? = null

        try {
            response = apiMethod(placeId)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        if (response?.isSuccessful != true) {
            Log.w(TAG, "response.isSuccessful isn't true.")
        }

        return response?.body()
    }

    suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
        return callSafely(client::getPlaceComments, placeId)
    }

    suspend fun deletePlaceComment(placeId: String): Unit? {
        return callSafely(client::deletePlaceComment, placeId)
    }

    suspend fun addPlaceComment(placeId: String, text: String): Unit? {
        return callSafely(client::addPlaceComment, placeId, text) // HERE LIES THE PROBLEM
        // I can't pass additional data because the method signature won't match with what's defined in callSafely()
    }
}

现在它运行得非常好,当然我也有自己的Activity和其ViewModel,ViewModel调用存储库中的方法等等。这并不重要。
重要的是添加地点评论需要额外的数据,例如评论的实际文本。获取和删除评论仅需要placeId,而添加评论时还需要其内容,即text。
我已经阅读过在Kotlin中传递vararg函数是不可能的。我也不想用像"List of params"这样的东西来混淆所有API方法,因为大多数情况下都是空的,只会引起混淆。
我可以走捷径,只需将callSafely的代码复制到addPlaceComment中并进行修改,但这不是我要寻找的东西。我知道如何解决问题,但我不知道如何以“干净的方式”解决问题。将来,我可能会添加一些需要额外数据(除了placeId)的端点,问题将再次出现。
在这种情况下,你会怎么做?如何以“正确的方式”编写它?
我甚至不确定如何恰当地表达我正在寻找的东西,这就是为什么这篇文章如此冗长。提前对此感到抱歉。我真的希望你能帮助我。
2个回答

5

“清洁的方式”是一个非常广泛的概念。一切都取决于你的需求,没有“唯一正确的方法”。

在你的特定情况下,你有几个选择:

1)类型别名

typealias ApiCall1<P, R> = suspend (P) -> Response<R>
typealias ApiCall2<P1, P2, R> = suspend (P1, P2) -> Response<R>

fun <P> callSafely(param: P, call: ApiCall1<P, YourResult>): YourResult
fun <P1, P2> callSafely(param1: P1, param2: P2, call: ApiCall2<P1, P2, YourResult>): YourResult

2) 可变参数

fun callSafely(vararg params: String, call: suspend (arr: Array<String>) -> YourResult {
   ...
   call(*params) 
   ...
}

3) Lambdas(对于你的情况更好)

没有人强迫你使用方法引用。需要时请使用lambda表达式,但将lambda表达式放置在最后一个参数位置以使代码更加“清晰”。

private suspend fun <T> callSafely(
    placeId: String,
    apiMethod: suspend (placeId: String) -> Response<T>
): T?

suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
    return callSafely(placeId, client::getPlaceComments)
}

suspend fun deletePlaceComment(placeId: String): Unit? {
    return callSafely(placeId, client::deletePlaceComment)
}

suspend fun addPlaceComment(placeId: String, text: String): Unit? {
    return callSafely(placeId) { id -> client.addPlaceComment(id, text) }
}

谢谢您,我知道“理想的解决方案”并不存在,但是选项3正是我一直在寻找的! - Bartek Pacia

1

试试这个:

class SocialRepository {
private val client: Api = ApiClient.webservice

private suspend fun <T> callSafely(
    apiMethod: suspend (placeId: String) -> Response<T>,
    vararg stringParams: String,
): T? {
    Log.d(TAG, "$apiMethod called safely")

    var response: Response<T>? = null

    try {
        response = apiMethod(stringParams[0])
    } catch (e: Exception) {
        e.printStackTrace()
    }

    if (response?.isSuccessful != true) {
        Log.w(TAG, "response.isSuccessful isn't true.")
    }

    return response?.body()
}

suspend fun getPlaceComments(placeId: String): List<CommentResponse>? {
    return callSafely(apiMethod= client::getPlaceComments, stringParams=*arrayOf(placeId))
}

suspend fun deletePlaceComment(placeId: String): Unit? {
    return callSafely(apiMethod=client::deletePlaceComment, stringParams=*arrayOf(placeId))
}

suspend fun addPlaceComment(placeId: String, text: String): Unit? {
    return callSafely(apiMethod = client::addPlaceComment,stringParams= *arrayOf(placeId,text))
}

}


2
请勿仅仅发布代码,因为这样没人能学到什么。解释一个解决方案可能和实际方案一样有用,所以请考虑在未来添加更多的解释。只是一个友善的提示 :) - a_local_nobody

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接