Android开发中的前五个代码异味:Jetpack Compose UI和MVVM

news2024/11/15 16:23:20

Android开发中的前五个代码异味:Jetpack Compose UI和MVVM

learn-kotlin

代码异味是指软件代码中潜在问题的指标,可能并不一定是错误,但可能会导致问题,如增加维护复杂性、降低性能或降低可读性。我们将探讨Android开发中的前五个代码异味,其中包括使用Jetpack Compose UI和Model-View-ViewModel(MVVM)架构的示例。

1. 上帝对象或上帝类:

上帝对象或上帝类是指试图做太多事情的单个类,违反了单一职责原则。在Android开发中,这可能会使代码更难理解、维护和修改。

示例:

我们将以一个充当“上帝对象”的ViewModel为例,然后使用存储库模式进行重构。

上帝对象ViewModel示例:

class GodObjectViewModel : ViewModel() {
    private val apiService = ApiService()
    private val database = AppDatabase.getInstance()

    // LiveData to expose data to the UI
    private val _data = MutableLiveData<List<DataItem>>()
    val data: LiveData<List<DataItem>> = _data

    init {
        loadData()
    }

    private fun loadData() {
        viewModelScope.launch {
            // Fetch data from API
            val apiData = apiService.getData()

            // Save data to database
            database.dataDao().insertAll(apiData)

            // Load data from database
            val dbData = database.dataDao().getAll()

            // Transform data
            val transformedData = transformData(dbData)

            // Update LiveData
            _data.value = transformedData
        }
    }

    private fun transformData(input: List<DataItem>): List<DataItem> {
        // Perform some transformations on the data
        // ...
        return transformedData
    }
}

使用仓库模式重构后的 ViewModel:

class RefactoredViewModel(private val dataRepository: DataRepository) : ViewModel() {
    // LiveData to expose data to the UI
    private val _data = MutableLiveData<List<DataItem>>()
    val data: LiveData<List<DataItem>> = _data

    init {
        loadData()
    }

    private fun loadData() {
        viewModelScope.launch {
            // Fetch data using the repository
            val transformedData = dataRepository.fetchData()

            // Update LiveData
            _data.value = transformedData
        }
    }
}

为了改进代码,我们使用仓库模式对 ViewModel 进行了重构。我们创建了一个独立的 DataRepository 类,它负责处理 API 调用、数据库操作和数据转换的职责。然后,重构后的 ViewModel 被简化为仅专注于通过委托数据获取任务到 DataRepository 来向 UI 公开数据。

2. 深层继承层次结构:

过度使用继承可能导致代码复杂且难以维护。在适当的情况下,应优先考虑组合而非继承。

示例:

使用 Kotlin 和 Jetpack Compose,展示了一个深层的继承层次结构,然后使用组合对其进行了重构。

abstract class BaseComposable {
    abstract fun DrawScope.draw()
}

class FirstLevelComposable : BaseComposable() {
    override fun DrawScope.draw() {
        // Implement some functionality
    }
}

class SecondLevelComposable : FirstLevelComposable() {
    override fun DrawScope.draw() {
        super.draw()
        // Add more functionality
    }
}

class ThirdLevelComposable : SecondLevelComposable() {
    override fun DrawScope.draw() {
        super.draw()
        // Add even more functionality
    }
}

@Composable
fun DeepInheritanceHierarchyExample() {
    val thirdLevelComposable = ThirdLevelComposable()

    Canvas(modifier = Modifier.fillMaxSize()) {
        thirdLevelComposable.draw()
    }
}

使用组合进行重构:

@Composable
fun BaseComposable() {
    // Implement some functionality
}

@Composable
fun FirstLevelComposable() {
    BaseComposable()
    // Add more functionality
}

@Composable
fun SecondLevelComposable() {
    FirstLevelComposable()
    // Add even more functionality
}

@Composable
fun CompositionExample() {
    Canvas(modifier = Modifier.fillMaxSize()) {
        SecondLevelComposable()
    }
}

在重构的示例中,我们使用组合替换了深度继承层次结构,使代码更易于理解和维护。我们创建了小型可重用的 Composable 函数,而不是从多个父类继承,可以组合这些函数来实现所需的功能。这种方法更加灵活,并遵循 Jetpack Compose 的原则。

3. 硬编码值或“魔法数字”:

硬编码值可能会使代码不够灵活,难以维护。使用常量或资源文件存储可能会发生更改的值。
示例:
在 Jetpack Compose 中,我们不使用像 colors.xml、dimens.xml 和 strings.xml 这样的 XML 文件。相反,我们在 Kotlin 文件中定义这些资源。以下是一个使用硬编码值的示例,并对其进行重构以使用常量:

硬编码代码示例:

import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun HardcodedValuesExample() {
    Text(
        text = "Hello, World!",
        color = Color(0xFF3F51B5),
        modifier = Modifier.padding(16.dp)
    )
}

使用常量进行重构:
创建一个专门的文件,例如Theme.kt

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

object Constants {
    val primaryColor = Color(0xFF3F51B5)
    val defaultPadding = 16.dp
}

更新 HardcodedValuesExample 使用常量:

import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import Constants.primaryColor
import Constants.defaultPadding

@Composable
fun RefactoredValuesExample() {
    Text(
        text = "Hello, World!",
        color = primaryColor,
        modifier = Modifier.padding(defaultPadding)
    )
}

在重构的示例中,我们使用在专门的文件中定义的常量来代替硬编码的值。这使得代码更易于维护和更新。对于文本字符串,您可以使用类似的方法,在单独的文件中定义字符串常量,并在您的 Composables 中使用它们。

4. 长方法或类:

长方法或类难以理解和维护。将它们拆分成更小、更集中的功能单元。
示例:
使用 Kotlin 和 Jetpack Compose,我们有一个 ViewModel 类,其中包含一个长方法,处理数据转换和业务逻辑。我们将通过将方法拆分为更小、更专注的函数来进行重构。
长方法示例:

class LongMethodViewModel : ViewModel() {
    // ...

    private fun processData(data: List<DataItem>): List<ProcessedDataItem> {
        // Step 1: Filter the data
        val filteredData = data.filter { item ->
            item.isActive && item.value > 10
        }

        // Step 2: Transform the data
        val transformedData = filteredData.map { item ->
            ProcessedDataItem(
                id = item.id,
                displayName = "${item.name} (${item.value})",
                important = item.value > 50
            )
        }

        // Step 3: Sort the data
        val sortedData = transformedData.sortedByDescending { item ->
            item.important
        }

        return sortedData
    }
}

重构成更小更专一的代码:

class RefactoredViewModel : ViewModel() {
    // ...

    private fun processData(data: List<DataItem>): List<ProcessedDataItem> {
        return data.filter(::filterData)
            .map(::transformData)
            .sortedByDescending(::sortData)
    }

    private fun filterData(item: DataItem): Boolean {
        return item.isActive && item.value > 10
    }

    private fun transformData(item: DataItem): ProcessedDataItem {
        return ProcessedDataItem(
            id = item.id,
            displayName = "${item.name} (${item.value})",
            important = item.value > 50
        )
    }

    private fun sortData(item: ProcessedDataItem): Boolean {
        return item.important
    }
}

在重构的例子中,我们将长的processData方法分解为更小、更专注的函数:filterData、transformData和sortData。这使得代码更易于理解、测试和维护。

5. 强耦合:

高度依赖彼此的类或组件可能会使修改或测试代码变得困难。通过设计具有良好定义接口和最小依赖关系的组件来实现松耦合。
例子:
使用Kotlin和Jetpack Compose,ViewModel和Composable函数之间存在强耦合。我们将使用StateFlow重构它以实现松耦合。
强耦合例子:

class TightCouplingViewModel : ViewModel() {
    val data = mutableStateOf(listOf<DataItem>())

    init {
        loadData()
    }

    private fun loadData() {
        // Load data...
        data.value = loadedData
    }
}

@Composable
fun TightCouplingExample(viewModel: TightCouplingViewModel) {
    val data = viewModel.data.value
    LazyColumn {
        items(data) { item ->
            // Display data item...
        }
    }
}

使用松散耦合进行重构:
更新 ViewModel 以使用 StateFlow:

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class LooseCouplingViewModel : ViewModel() {
    private val _data = MutableStateFlow(listOf<DataItem>())
    val data: StateFlow<List<DataItem>> = _data

    init {
        loadData()
    }

    private fun loadData() {
        // Load data...
        _data.value = loadedData
    }
}

更新 Composable 函数以观察来自 ViewModel 的数据:

import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue

@Composable
fun LooseCouplingExample(viewModel: LooseCouplingViewModel) {
    val data by viewModel.data.collectAsState()
    LazyColumn {
        items(data) { item ->
            // Display data item...
        }
    }
}

在重构的示例中,我们通过使用StateFlow来观察和响应ViewModel中数据的变化,实现了ViewModel和Composable函数之间的松耦合。这种方法使得修改或测试代码更容易,因为ViewModel和UI组件不会直接引用彼此。

结论

总之,在Android开发中解决五大代码坏味道有助于创建更清晰、更高效、更高质量的代码库。通过遵循Jetpack Compose UI和MVVM架构的最佳实践,开发人员可以创建具有模块化、可维护和可测试性的应用程序。

  1. 通过将大型类分解为更小、更集中的功能单元,实现避免过多使用god对象,并实现存储库模式。
  2. 通过组合取代深层继承层次结构,创建更小、可重用的可组合项或混合项,以实现所需的功能。
  3. 通过将硬编码的值或“神奇数字”替换为常量或资源文件,使代码更具灵活性和可维护性。
  4. 通过将长方法或类分解为更小、更集中的功能单元,使其更易于理解、测试和维护。
  5. 通过设计具有良好定义接口和最小依赖关系的组件、使用LiveData或StateFlow来观察和响应ViewModel中的数据变化,并避免ViewModel和UI组件之间的直接引用,实现松耦合。
    通过解决这些代码坏味道,您可以增强开发过程,改善可维护性,并创建提供优秀用户体验的Android应用程序。

参考

https://medium.com/@fauzisho/top-5-code-smells-in-android-development-jetpack-compose-ui-and-mvvm-e3934ddbc14c

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

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

相关文章

【C++】布隆过滤器

文章目录 布隆过滤器提出布隆过滤器概念布隆过滤器应用场景设计思路:布隆过滤器的插入布隆过滤器的查找布隆过滤器删除BloomFilter.h布隆过滤器优点布隆过滤器缺陷 布隆过滤器提出 我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经…

Leetcode力扣秋招刷题路-0902

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 902. 最大为 N 的数字组合 给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如&#xff0c;如果 digits [‘1’,‘3’,‘5’]&#xff0c;我…

一图看懂 requests 模块:用Python编写、供人类使用的HTTP库, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 requests 模块&#xff1a;用Python编写、供人类使用的HTTP库, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【requests】统计常量str 模块3 w…

小红书违禁词有哪些,小红书违禁词汇总分享

大家都知道小红书平台对于违禁词的管控一向非常严格&#xff0c;笔记中一旦出现就可能被限流&#xff0c;今天为大家整理了一份小红书违禁词汇总&#xff0c;希望能够帮助大家避免被限流。 小红书违禁词汇总大致有以下几个分类&#xff0c;大家平时写笔记的时候最好避开这些词或…

HashMap底层实现原理

HashMap HashMap 最早出现在 JDK 1.2中&#xff0c;底层基于散列算法实现&#xff0c;它是一个key-value结构的容器。 是一个key-value的映射容器&#xff0c;key不重复jdk8中的HashMap基于数组链表红黑树实现不保证键值的顺序可以存入null值非线程安全&#xff0c;多线程环境…

log4j2.xml配置解析

log4j2.xml文件的配置大致如下&#xff1a; Configuration&#xff1a;为根节点&#xff0c;有status和monitorInterval等多个属性 status的值有 “trace”, “debug”, “info”, “warn”, “error” and “fatal”&#xff0c;用于控制log4j2日志框架本身的日志级别&#x…

python+vue+nodejs旅游资源信息网站

1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和验证码&#xff0c;然后对登录进来的用户判断身份信息&#xff0c;判断是管理员用户还是普通用户。 2&#xff0e;系统用户管理&#xff1a;不管是…

如何选择正确的数据可视化图表

数据可视化是数据分析的重要组成部分&#xff0c;因为它们能够以图形格式有效地汇总大量数据。有许多可用的图表类型&#xff0c;每种类型都有自己的优势和用例。分析过程中最棘手的部分之一是选择使用这些可视化效果之一的正确方法来表示数据。 在本文中&#xff0c;我们根据需…

基于redis和threadlocal实现登录状态校验和拦截

1.流程图 单机节点下的登录状态校验 分布式节点下的登录状态校验 2.代码实现 实现步骤分为如下几步 实现WebMvcConfigurer接口&#xff0c;添加拦截器定义拦截器&#xff0c;需要配置两个interceptor&#xff0c;第一个用于刷新token&#xff0c;写threadlocal&#xff…

AI绘图实战(八):制作游戏人物原稿三视图 | Stable Diffusion成为设计师生产力工具

S&#xff1a;AI能取代设计师么&#xff1f; I &#xff1a;至少在设计行业&#xff0c;目前AI扮演的主要角色还是超级工具&#xff0c;要顶替&#xff1f;除非甲方对设计效果无所畏惧~~ 预先学习&#xff1a; 安装及其问题解决参考&#xff1a;《Windows安装Stable Diffusion …

【机器学习 - 10】:PCA和梯度上升法

文章目录 了解PCA使用梯度上升法求解第一主成分使用梯度上升法求解第二主成分求数据前n个主成分使用sklearn中封装的PCA使用真实数据集 了解PCA PCA的概念&#xff1a;主成分分析(Principal Component Analysis&#xff0c;PCA)&#xff0c;是一种统计方法。通过正交变换将一组…

10分钟如何轻松掌握JMeter使用方法?

目录 引言 安装jmeter HTTP信息头管理器 JMeter断言 HTTP请求默认值来代替所有的域名与端口 JSON提取器来替换变量 结语 引言 想要了解网站或应用程序的性能极限&#xff0c;JMeter是一个不可或缺的工具。但是&#xff0c;对于初学者来说&#xff0c;该如何上手使用JMe…

【LLM】低成本部署大语言模型, 并且还能达到部署在GPU上差不多的效果

目录 前言 部署 效果 问题1&#xff1a;人类为什么需要睡觉&#xff1f; 问题2&#xff1a;世界上最高的山峰是什么&#xff1f; 前言 点进来看本文的应该都知道模型对硬件的要求很高, 那我也不废话了, 直接安排最近发现的一个开源项目, 它可以帮助我们降低部署模型的成…

按摩仪市场的AB面:暗战不止,迷雾未散

配图来自Canva可画 由于生活节奏的加快以及来自各方的压力&#xff0c;再加上熬夜、长时间低头玩手机等不良生活习惯&#xff0c;导致不少人的身体都出现了亚健康状态。不过&#xff0c;随着当下健康理念逐渐深入人心&#xff0c;人们对于健康的重视程度也持续提升。无论是刘畊…

如何挖到人生中第一个漏洞?保姆级漏洞挖掘教学

前言 有不少阅读过我文章的伙伴都知道&#xff0c;我从事网络安全行业已经好几年&#xff0c;积累了丰富的经验和技能。在这段时间里&#xff0c;我参与了多个实际项目的规划和实施&#xff0c;成功防范了各种网络攻击和漏洞利用&#xff0c;提高了安全防护水平。 也有很多小…

Java微服务商城高并发秒杀项目--013.SentinelResource的使用

在shop-order-server模块中新建AnnoController&#xff1a; RestController Slf4j public class AnnoController {RequestMapping("/anno1")SentinelResource(value "anno1",blockHandler"anno1BlockHandler",fallback "anno1Fallback&qu…

使用Stream流写出优雅的高质量代码

前言 我们在开发中会大量的用到集合&#xff0c;少不了对集合进行一些操作&#xff0c;如何优雅的遍历集合&#xff0c;操作集合&#xff0c;不仅能体现出代码的质量&#xff0c;更能体现出程序员本身对自我的要求。 文章目录 前言一、Stream初体验二、Stream流的使用2.1 获取…

python实现人脸识别(face_recognition)

一、定义 1、介绍 本项目是世界上最强大、简洁的人脸识别库&#xff0c;你可以使用Python和命令行工具提取、识别、操作人脸。 本项目的人脸识别是基于业内领先的C开源库dlib中的深度学习模型&#xff0c;用Labeled Faces in the Wild人脸数据集进行测试&#xff0c;有高达99…

ESP32(一):Win10配置 IDF+VSCode

一、安装包下载&#xff1a; Git&#xff1a;Git for WindowsPython&#xff1a;Download Python | Python.org以Windows x86-64开头的是 64 位的 Python 安装程序&#xff1b;以Windows x86开头的是 32 位的 Python 安装程序。ESP-IDF&#xff08;选择Offline版本&#xff09…

Kettle安装与使用

一、Kettle简介 Kettle最早是一个开源的ETL&#xff08;Extract-Transform-Load的缩写&#xff09;工具&#xff0c;全称为KDE Extraction, Transportation, Transformation and Loading Environment。后来Kettle重命名为Pentaho Data Integration 。它由Java开发&#xff0c;…