如何在不违反开闭原则的前提下适配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
相关产品推荐
相关产品推荐

