使用 Kotlin 委托,拆分比较复杂的 ViewModel
需求背景
- 在实际的开发场景中,一个页面的数据,可能是由多个业务的数据来组成的。
- 使用 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
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 负责处理对应的业务逻辑。
总结
这里只是提供一种拆分 ViewModel 的思路,在项目中进行应用的话,可以根据需要进行改造。
参考文章
slicing-your-viewmodel-with-delegates
作者:入魔的冬瓜
来源:juejin.cn/post/7213257917254860861
来源:juejin.cn/post/7213257917254860861