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

news2024/11/17 7:29:38

需求背景

在这里插入图片描述

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/517876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Javascript学习- DOM获取属性操作

Web API 基本认知 作用和分类 作用: 就是使用 JS 去操作 html 和浏览器分类:DOM (文档对象模型)、BOM(浏览器对象模型) 什么是DOM DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的API 白话文:DOM是浏览器提供的一套专门用来操作网…

open3d-ml 读取SemanticKITTI Dataset

目录 1. 下载dataset 2. 读取并做可视化 3. 源码阅读 3.1 读取点云数据-bin格式 3.2 读取标注数据-.label文件 3.3 读取配置 3.4 test 3.5 train 1. 下载dataset 以SemanticKITTI为例。下载链接&#xff1a;http://semantic-kitti.org/dataset.html#download 把上面三…

如何编写有效的FAQ常见问题页面

FAQ&#xff08;Frequently Asked Questions&#xff09;常见问题页面是网站或应用程序中经常使用的一种页面类型。它为用户提供了一种便捷的方式来寻找解决问题的答案&#xff0c;同时也减轻了客服和支持团队的工作量。下面将介绍如何编写有效的FAQ常见问题页面。 明确受众 在…

网站域名历史记录批量查询-老域名建站历史快照数据查询

域名建站历史查询软件 域名建站历史查询软件是一种用于查询一个域名被使用的网站的历史记录的工具。它可以提供许多有用的信息&#xff0c;包括该网站的创建和修改日期、使用的网站建设平台、使用的CMS系统、网站的历史页面内容和页面结构等。 域名建站历史查询软件的作用是帮…

去阿里、百度、网易大厂的软件测试工程师都是什么人?卷起来...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试&#xff0c;…

CRA应用使用craco实现webpack配置+module.less+src别名配置

前言&#xff1a;总所周知&#xff0c;create-react-app 是目前最受欢迎的创建 React 应用的手脚架之一&#xff0c;下面简称CRA&#xff1b;CRA 最推荐更改 webpack 配置的当属使用 craco&#xff0c;下面我们一起来看看配置吧。 一、创建 cra 应用 npx create-react-app my…

全国标杆!3DCAT实时云渲染助力深圳移动5G+智慧校园建设

2023年2月27日&#xff0c;中国移动在陕西西安召开全国教育行业全年工作部署暨电子学生证专项调度会&#xff0c;来自全国各地的移动分公司、专家、合作伙伴等参加了会议。瑞云科技旗下3DCAT实时渲染云作为中国移动的重要合作伙伴之一&#xff0c;也受邀出席进行项目展示。 在会…

算法修炼之练气篇——练气五层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

WooCommerce电商开发:高性能订单存储(即将成为)新常态

要创建免费网站&#xff1f;从易服客建站平台免费开始 500M免费空间&#xff0c;可升级为20GB电子商务网站 创建免费网站 WooCommerce电商开发&#xff1a;高性能订单存储&#xff08;即将成为&#xff09;新常态 发布于 2023年3月30日 自2022年1月以来&#xff0c;我们一…

【企业信息化】第5集 免费开源ERP: Odoo 16 inventory仓库管理系统 现代化线上仓库管理软件

文章目录 前言一、概览二、硬件1.设置2.移动扫描仪3.USB / 蓝牙4.标签打印机5.体重秤 三、总结 前言 现代化线上仓库管理软件。 一、概览 提高业绩&#xff0c;缩短处理时间 通过复式分录智能库存系统更好地整理您的仓库。 获得最高效的存货方法并改善您的全部内部运营。Odoo…

2023年深圳/东莞/惠州CPDA数据分析师认证报名到哪里?

CPDA数据分析师认证是大数据方面的认证&#xff0c;助力数据分析人员打下扎实的数据分析基础知识功底&#xff0c;为入门数据分析保驾护航。 帮助数据分析人员掌握系统化的数据分析思维和方法论&#xff0c;提升工作效率和决策能力&#xff0c;遇到问题能够举一反三&#xff0c…

介绍 std::vector 的 operator[]

介绍 std::vector::operator[] 操作符只能 访问指定的元素 std::vector<T,Allocator>::operator[] ------------------------------------- reference operator[]( size_type pos ); //(until C20) constexpr reference operator[]( size_type po…

【广州华锐互动】3D可视化技术提升城市规划效率,打造智慧城市

城市规划是一个复杂而又多变的过程&#xff0c;需要考虑众多因素&#xff0c;如地形、气候、经济、人口等。传统的城市规划信息展示方式主要是通过平面图、立面图等二维图像来呈现城市规划的方案和效果&#xff0c;难以全面展示城市规划信息的空间特征和复杂性。 利用3D可视化…

你真的会性能测试吗?性能测试需求分析,从业务到数据(详细)...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 产品需求 业务场…

MFC二维码生成,libqrencode——小白级

直接使用&#xff1a;下载我编译好的库-链接-&#xff0c;直接跳转到使用库那一步 两个工程打包链接 ——下载 先上最终效果&#xff1a; ​​ 二维码生成&#xff0c;参考&#xff1a; 跳转链接 一、 libqrencode库编译 libqrencode是一个日本人写的的库&#xff0c;库在这…

自学黑客(网络安全)看这篇就够了

写了这么多编程环境和软件安装的文章&#xff0c;还有很多小伙伴在后台私信说看不懂。我都有点头疼了&#xff0c;但是小伙伴们求学的心情我还是能理解&#xff0c;竟然这么多人给我反馈了&#xff0c;那我就再写一篇网络安全自学的教程吧&#xff01;大家耐心看完&#xff0c;…

Fidder 抓iPhone Andorra包 教程 解决证书过期问题

1. 下载Fidder 链接&#xff1a;https://pan.baidu.com/s/12xgEU8YyE-CfWMbPIWqWMw?pwdfhxh 2.设置Fidder 3. 手机设置代理并安装证书 首先 windows r 键 输入cmd 输入 ipconfig 查看本机的IPv4地址 然后设置手机网络的HTTP代理。之后在iPhone上用Safari访问 ipv4地址:888…

【mysql】2003-Can‘t connect to MySQL server on “XX.XX.XX.XX“ (10060 unknow error)

使用navicat或者其他数据库管理工具连接远程mysql服务器时出现2003-Can’t connect to MySQL server (10060 unknow error)错误 经过排查时发现是防火墙的问题&#xff0c;OK&#xff01;那么下面写一下处理的过程 1、查看防火墙的状态 systemctl status firewalld 2、 开放…

MySQL基础(二十九)数据库的设计规范

1 范式 1.1 范式简介 在关系型数据库中&#xff0c;关于数据表设计的基本原则、规则就称为范式。可以理解为&#xff0c;一张数据表的设计结 构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关系型数据库&#xff0c;必须满足一定的范式。 1.2 范式都包括哪些 目…

连接器行业最新状况:竞争充分,行业集中度不断提升

随着应用领域不断扩展&#xff0c;连接器产业逐渐发展成为产品种类齐全、品种规格丰富、专业方向细分、行业特征明显、标准体系规范、系列化及专业化的行业。 连接器行业是充分竞争的行业&#xff0c;行业集中度不断提升 连接器行业具有市场全球化和分工专业化的特征&#xf…