使用 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,使用起来会比较麻烦。

如需 kotlin 学习笔记 请点击此处免费获取

优化实现

目标:

  • 将 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 的思路,在项目中进行应用的话,可以根据需要进行改造。

猜你喜欢

转载自blog.csdn.net/m0_71506521/article/details/130642122