You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不违反开闭原则的前提下适配MVP中Model的异步需求?

如何在MVP模式下避免因同步/异步切换违反开闭原则?

问题场景

最初基于MVP开发时,Model使用假数据同步返回结果:

Model 实现(假数据同步版)

override fun getAllItem(): List<Item> {
    val obj = JSONObject(data)
    val itemsArray = obj.getJSONArray("items").toString()
    val gson = GsonBuilder().create()

    return gson.fromJson(
        itemsArray, object : TypeToken<List<Item>>() {}.type
    )
}

Contract 接口(同步版)

interface Model {
    fun getAllItem(): List<Item>
}

改用Volley实现网络请求时,由于异步特性需要引入回调接口,导致必须修改原Contract和Model方法签名,违反开闭原则:

Model 实现(Volley异步版)

override fun getAllItem(result: Contract.Model.ReceivedDataItems) {
    val queue = Volley.newRequestQueue(context)
    val stringRequest = StringRequest(Request.Method.GET, baseUrl,
        {
            val gson = GsonBuilder().create()
            val itemsList = gson.fromJson<List<Item>>(it,
                object : TypeToken<List<Item>>() {}.type
            )
            result.onSuccessItems(itemsList)
        },
        {
            result.onFailed()
        })
    
    queue.add(stringRequest)
}

Contract 接口(异步版)

interface Model {
    fun getAllItem(result: ReceivedDataItems)
    
    interface ReceivedDataItems {
        fun onSuccessItems(items: List<Item>)
        fun onFailed()
    }
}

解决与预判方案

1. 初期就定义异步优先的契约接口

不管初期用假数据还是真实接口,都优先设计支持异步的Contract,哪怕假数据是同步获取的,也通过回调返回结果。这样后续切换真实网络请求时,无需修改接口,仅替换Model实现即可。

修改后的假数据Model实现

override fun getAllItem(result: Contract.Model.ReceivedDataItems) {
    val obj = JSONObject(data)
    val itemsArray = obj.getJSONArray("items").toString()
    val gson = GsonBuilder().create()
    val items = gson.fromJson(
        itemsArray, object : TypeToken<List<Item>>() {}.type
    )
    // 模拟异步回调,直接返回假数据
    result.onSuccessItems(items)
}

对应的Contract从一开始就采用带回调的版本,Presenter的调用逻辑也无需后续修改,完全符合开闭原则。

2. 用抽象封装数据获取的执行方式

定义一个抽象的DataFetcher接口,封装同步/异步的执行逻辑,Model依赖这个抽象而非具体实现。比如:

interface DataFetcher<T> {
    fun fetch(callback: FetchCallback<T>)
}

interface FetchCallback<T> {
    fun onSuccess(data: T)
    fun onFailed()
}

假数据实现FakeDataFetcher,网络请求实现VolleyDataFetcher,Model仅依赖DataFetcher,切换数据源时只需替换DataFetcher实例,无需修改Model的方法签名。

3. 用响应式框架统一同步/异步处理

借助LiveData、RxJava或Kotlin Flow等响应式框架,将数据获取逻辑统一包装成可观察的流。比如用Flow:

Contract 接口

interface Model {
    fun getAllItem(): Flow<List<Item>>
}

假数据Model实现

override fun getAllItem(): Flow<List<Item>> {
    val obj = JSONObject(data)
    val itemsArray = obj.getJSONArray("items").toString()
    val gson = GsonBuilder().create()
    val items = gson.fromJson(
        itemsArray, object : TypeToken<List<Item>>() {}.type
    )
    // 同步数据转成Flow
    return flow { emit(items) }
}

Volley网络请求实现

override fun getAllItem(): Flow<List<Item>> = callbackFlow {
    val queue = Volley.newRequestQueue(context)
    val stringRequest = StringRequest(Request.Method.GET, baseUrl,
        { response ->
            val gson = GsonBuilder().create()
            val itemsList = gson.fromJson<List<Item>>(response,
                object : TypeToken<List<Item>>() {}.type
            )
            trySend(itemsList).isSuccess
            close()
        },
        { error ->
            close(error)
        })
    queue.add(stringRequest)
    awaitClose { queue.cancelAll(this) }
}

这种方式下,同步和异步逻辑对外暴露统一的Flow接口,Presenter只需订阅流即可,无需关心底层是同步还是异步实现。

4. 提前预判数据来源的不确定性

  • 需求初期就考虑所有可能的数据源:本地假数据、本地数据库、网络请求,这些场景的执行方式(同步/异步)、失败概率不同,接口设计要兼容所有情况。
  • 永远在契约中预留异常处理接口:哪怕初期假数据不会失败,也要定义onFailed这类回调,避免后续新增失败场景时修改接口。
  • 遵循依赖倒置原则:Contract定义抽象行为,而非具体的返回格式或执行方式,确保后续替换实现时不影响上层调用。

内容的提问来源于stack exchange,提问作者sara_asadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 14:40:29