【问题标题】:Kotlin: how to pass a function with varying arguments as a parameter to other functionKotlin:如何将具有不同参数的函数作为参数传递给其他函数
【发布时间】:2020-01-26 21:41:13
【问题描述】:

所以,我正在重写我的应用程序代码 te 是“干净的”(分层,遵循 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 调用存储库中的方法等。没关系。

重要的是添加地点评论需要额外的数据,例如评论的实际文本。获取和删除cmets只需要placeId,而添加评论时,它的内容,它的text也是必需的。 我读过在 Kotlin 中传递 vararg 函数是不可能的。我也不希望使用 List of params 之类的东西来混淆所有 API 方法,因为大多数情况下它是空的,只会造成混乱。

我可以走简单的路,只需将callSafely 的代码复制到addPlaceComment 并更改它,但这不是我想要的。我知道如何解决问题,但我不知道该怎么做the clean way。将来我可能会添加更多需要额外数据的端点(placeId 除外),问题将再次出现。

在这种情况下你会怎么做? “正确的方式”怎么写?

我什至不知道如何正确地表达我在寻找什么,这就是为什么这篇文章如此漫无边际。对此提前表示抱歉。我真的希望你能帮助我。

【问题讨论】:

    标签: android generics kotlin retrofit


    【解决方案1】:

    “干净的方式”是一个非常宽泛的概念。一切都取决于您的需求,没有“唯一的好方法”。

    在您的特定情况下,您有多种选择:

    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 正是我一直在寻找的!
    【解决方案2】:

    试试这个:

    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))
    }
    

    }

    【讨论】:

    • 不要只是发布代码,如果您只是发布代码,没有人会学到任何东西。解释解决方案可能与实际解决方案一样有用,因此请考虑在将来添加更多解释。只是一个友好的提示:)
    猜你喜欢
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-13
    • 1970-01-01
    • 2013-01-27
    • 2014-06-04
    相关资源
    最近更新 更多