注册

使用 Kotlin 委托,拆分比较复杂的 ViewModel

需求背景




  1. 在实际的开发场景中,一个页面的数据,可能是由多个业务的数据来组成的。
  2. 使用 MVVM 架构进行实现,在 ViewModel 中存放和处理多个业务的数据,通知 View 层刷新 UI。

传统实现


比如上面的例子,页面由3 个模块数据构成。


我们可以创建一个 ViewModel ,以及 3个 LiveData 来驱动刷新对应的 UI 。


    class HomeViewModel() : ViewModel() {

private val _newsViewState = MutableLiveData<String>()
val newsViewState: LiveData<String>
get() = _newsViewState

private val _weatherState = MutableLiveData<String>()
val weatherState: LiveData<String>
get() = _weatherState

private val _imageOfTheDayState = MutableLiveData<String>()
val imageOfTheDayState: LiveData<String>
get() = _imageOfTheDayState

fun getNews(){}
fun getWeather(){}
fun getImage(){}

}

这样的实现会有个缺点,就是随着业务的迭代,页面的逻辑变得复杂,这里的 ViewModel 类代码会变复杂,变得臃肿。


这个时候,就可能需要考虑进行拆分 ViewModel


一种实现方法,就是直接简单地拆分为3个 ViewModel,每个 ViewModel 处理对应的业务。但是这样会带来其他问题,就是在 View 层使用的时候,要判断当前是什么业务,然后再去获取对应的ViewModel,使用起来会比较麻烦。


优化实现


目标:



  • 将 ViewModel 拆分成多个子 ViewModel,每个子 ViewModel 只关注处理自身的业务逻辑
  • 尽量考虑代码的可维护性、可扩展性

Kotlin 委托



  • 委托(Delegate)是 Kotlin 的一种语言特性,用于更加优雅地实现代理模式
  • 本质上就是使用了 by 语法后,编译器会帮忙生成相关代码。
  • 类委托: 一个类的方法不在该类中定义,而是直接委托给另一个对象来处理。
  • 基础类和被委托类都实现同一个接口,编译时生成的字节码中,继承自 Base 接口的方法都会委托给BaseImpl 处理。

// 基础接口
interface Base {
fun print()
}

// 基础对象
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

// 被委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 最终调用了 Base#print()
}

具体实现


定义子 ViewModel 的接口,以及对应的实现类


    interface NewsViewModel {
companion object {
fun create(): NewsViewModel = NewsViewModelImpl()
}

val newsViewState: LiveData<String>

fun getNews()
}

interface WeatherViewModel {
companion object {
fun create(): WeatherViewModel = WeatherViewModelImpl()
}

val weatherState: LiveData<String>

fun getWeather()
}

interface ImageOfTheDayStateViewModel {
companion object {
fun create(): ImageOfTheDayStateViewModel = ImageOfTheDayStateImpl()
}

val imageState: LiveData<String>

fun getImage()
}

class NewsViewModelImpl : NewsViewModel, ViewModel() {
override val newsViewState = MutableLiveData<String>()

override fun getNews() {
newsViewState.postValue("测试")
}
}

class WeatherViewModelImpl : WeatherViewModel, ViewModel() {
override val weatherState = MutableLiveData<String>()

override fun getWeather() {
weatherState.postValue("测试")
}
}

class ImageOfTheDayStateImpl : ImageOfTheDayStateViewModel, ViewModel() {
override val imageState = MutableLiveData<String>()

override fun getImage() {
imageState.postValue("测试")
}
}


  • 把一个大模块,划分成若干个小的业务模块,由对应的 ViewModel 来进行处理,彼此之间尽量保持独立。
  • 定义接口类,提供需要对外暴漏的字段和方法
  • 定义接口实现类,内部负责实现 ViewModel 的业务细节,修改对应字段值,实现相应方法。
  • 这种实现方式,就不需要像上面的例子一样,每次都要多声明一个带划线的私有变量。并且可以对外隐藏更多 ViewModel 的实现细节,封装性更好

组合 ViewModel


image.png


    interface HomeViewModel : NewsViewModel, WeatherViewModel, ImageOfTheDayStateViewModel {
companion object {
fun create(activity: FragmentActivity): HomeViewModel {
return ViewModelProviders.of(activity, object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass == HomeViewModelImpl::class.java) {
@Suppress("UNCHECKED_CAST")

val newsViewModel = NewsViewModel.create()
val weatherViewModel = WeatherViewModel.create()
val imageOfTheDayStateImpl = ImageOfTheDayStateViewModel.create()

HomeViewModelImpl(
newsViewModel,
weatherViewModel,
imageOfTheDayStateImpl
) as T
} else {
modelClass.newInstance()
}

}
}).get(HomeViewModelImpl::class.java)
}
}
}

class HomeViewModelImpl(
private val newsViewModel: NewsViewModel,
private val weatherViewModel: WeatherViewModel,
private val imageOfTheDayState: ImageOfTheDayStateViewModel
) : ViewModel(),
HomeViewModel,
NewsViewModel by newsViewModel,
WeatherViewModel by weatherViewModel,
ImageOfTheDayStateViewModel by imageOfTheDayState {

val subViewModels = listOf(newsViewModel, weatherViewModel, imageOfTheDayState)

override fun onCleared() {
subViewModels.filterIsInstance(BaseViewModel::class.java)
.forEach { it.onCleared() }
super.onCleared()
}
}


  • 定义接口类 HomeViewModel,继承了多个子 ViewModel 的接口
  • 定义实现类 HomeViewModelImpl,组合多个子 ViewModel,并通过 Kotlin 类委托的形式,把对应的接口交给相应的实现类来处理
  • 通过这种方式,可以把对应模块的业务逻辑,拆分到对应的子 ViewModel 中进行处理
  • 如果后续需要新增一个新业务数据,只需新增相应的子模块对应的 ViewModel,而无需修改其他子模块对应的 ViewModel。
  • 自定义 ViewModelFactory,提供 create 的静态方法,用于外部获取和创建 HomeViewModel。

使用方式



  • 对于 View 层来说,只需要获取 HomeViewModel 就行了。
  • 调用暴露的方法,最后会委托给对应子 ViewModel 实现类进行处理。

        val viewModel = HomeViewModel.create(this)

viewModel.getNews()
viewModel.getWeather()
viewModel.getImage()

viewModel.newsViewState.observe(this) {

}
viewModel.weatherState.observe(this) {

}
viewModel.imageState.observe(this) {

}

扩展



  • 上面的例子,HomeViewModel 下面,可以由若干个子 ViewMdeol 构成。
  • 随着业务拓展,NewsViewModel、WeatherViewModel、ImageOfTheDayStateViewModel,也可能是分别由若干个子 ViewModel 构成。那也可以参照上面的方式,进行实现,最后会形成一棵”ViewModel 树“,各个节点的 ViewModel 负责处理对应的业务逻辑。

image.png


总结


这里只是提供一种拆分 ViewModel 的思路,在项目中进行应用的话,可以根据需要进行改造。


参考文章


slicing-your-viewmodel-with-delegates


Kotlin | 委托机制 & 原理 & 应用 - 掘金


作者:入魔的冬瓜
来源:juejin.cn/post/7213257917254860861

0 个评论

要回复文章请先登录注册