动态网站制作流程/网络热词2022
Clean 架构下的现代 Android 架构指南
Clean 架构是 Uncle Bob 提出的一种软件架构,Bob 大叔同时也是 SOLID 原则的命名者。
Clean 架构图如下:
这张图描述的是整个软件系统的架构,而不是单体软件,其中至少包括服务端以及客户端。
对于 Android 单体应用开发来说应该还需要一个更贴切更精确的 Clean 架构图。
我大概总结了一下过往的开发经验,找出了应用架构中的重要部分,然后绘制了下面这张 Clean 架构指导下的 Android 应用架构图:
以及一般数据流向图:
依赖关系
Clean 架构基本准则是源码级别的内层不依赖外层,依赖关系永远是单向的,外层向内层依赖。
如上,Model 层是没有任何依赖的,UseCase 可以依赖 Model 和 Repo 等,但绝不能依赖 ViewModel,UI 层依赖 ViewModel,但 ViewModel 绝不能依赖 UI 层。
为了达到这种源码级别的依赖关系,我们必须借助一些工具来实现依赖注入,一般可以使用 Hilt 或者 Koin 这样的框架来实现。
另外,依赖注入不应该被滥用,不是所有的对象都适合用依赖注入,只有那些有明确层次关系的模块,互相有着明确的依赖关系的才需要。对于一些工具类,显然是没必要注入的。
Model(领域模型)
业务模型,或者叫领域模型,是根据软件业务设计出来的具体模型,一般来说会是个 data class
,其中不包含任何业务逻辑,只是个单纯的模型对象。
由于是在整个架构的最内层,所以不依赖任何其他模块,并且相对稳定,设计的时候需要考虑这点。如果模型发生变化,那意味着整个上层的依赖方都可能发生变化,需要重新测试。
在命名和包结构上,领域模型不需要带 Entity 之类的后缀,直接命名为像 User 一样即可,但考虑到这是在软件的最内层,可能会被所有模块依赖到,所以要尽可能贴近其设计目标,并且不能太过宽泛。在包结构上,需要被存放在 model 包下面。
Adapter(数据适配器)
数据适配器层主要用来做数据转换,主要有两个职责:
- 转换网络接口实体数据类和领域模型。
- 领域模型之间的互相转换。
Adapter 层也比较纯粹,只负责简单的数据转换,而且对外暴漏的函数都是幂等函数。
如果数据转换过程中涉及到复杂的业务逻辑,可以考虑先用 UseCase 处理完成后再交给 Adapter。但因为 Adapter 层比 UseCase 层更靠内,所以 Adapter 不能依赖 UseCase。
习惯上,我们会以待转换类为开头,Adapter 结尾命名,例如我们要把 UserEntity
转换为 User
, 那么应该这么写:
class UserEntityAdapter{fun toUser(entity: UserEntity): User {//...}
}
Repo
对于我们 Android 开发来说,Repo 层应该是对网络接口或本地磁盘的数据读写的封装,对于 Repo 的使用者来说,不需要关注具体的实现,且 Repo 中一般不具备复杂的业务逻辑,只能包含简单的数据处理逻辑。
Repo 应当隐藏具体的实现细节,不仅包括获取方式是网络还是本地数据,也应该隐藏对应的实体数据类,这意味着 **Repo 层对外暴漏的函数的入参和出参不能包含接口返回的实体类,也不应该包含数据库表实体类,只能包含领域模型或者基本类型。**我们给 Room 设计的数据库表的 data class
应该限制在 Repo 内部,我们给 Retrofit 设计的接口返回数据 data class
也同样应该限制在 Repo 内部。
data class UserEntity(val name: String, val avatar: String)interface UserService {@GET("/user")suspend fun getUserInfo(@query("id") id: String): UserEntity
}data class User(val name: String, val avatar: String)class UserEntityAdapter @Inject constructor() {fun toUser(entity: UserEntity): User {return User(name = entity.name, avatar = entity.avatar)}
}class UserRepo @Inject constructor(private val userEntityAdapter: UserEntityAdapter,
) {private val userService: UserService by lazy {retrofit.create(UserService::class.java)}suspend fun getUser(id: String): User {return userService.getUserInfo(id).let(userEntityAdapter::toUser)}
}
除了上面说的相应数据的转换之外,请求数据也需要在 Repo 层转换,对于 Post 请求来说,可能会存在一个请求实体,这个实体数据类最好也不要对外暴漏,可以在 Repo 层的请求方法入参那里做一些转换,最好能让入参更简单友好。
Repo 层还有一个作用就是负责把从接口或者数据库中出来的不友好的数据模型转换成友好的数据模型。
另外,现在由于有了 BFF 的存在,在某些比较简单的业务场景下我们可以为了方便做一些妥协,也就是接口的响应数据实体类可以穿透 Repo 层,直接给到 ViewModel,甚至是 UiState 使用,但应该明白这只是为了方便的妥协,并不是最佳实践,需要严格控制影响范围。
UseCase(用例)
UseCase 一般是指特定应用场景下的业务逻辑,用例引导了数据在模型之间的输入输出,并且指挥着业务实体利用其中的关键业务逻辑来实现用例的设计目标。
因此,一个 UseCase 往往只包含一段具体的业务逻辑,他的输入是基本类型或者领域模型,输出也是,并且是幂等函数,也就是纯函数,所以 Google 建议我们每个 UseCase 只包含一个公开的函数,类似于下面这种写法:
class DoSomethingUseCase {operator fun invoke(xxx: Foo): Bar {// ...}
}
通过利用 Kotlin 特性来使 UseCase 在使用的时候达到直接使用函数的体感。
但考虑到依赖以及管理问题,UseCase 最好还是不要直接使用函数来实现,应当按照上面的方式,定义一个类,然后再暴露一个通过操作符重载的函数。
在使用 UseCase 时可以这么用:
class LoginViewModel @Inject constructor(private val doSomething: DoSomethingUseCase,
): ViewModel(){fun onLoginClick(){doSomething()}
}
UseCase 的问题
UseCase 的粒度非常细,基本上每个 UseCase 就是一个函数,在复杂的业务背景下将会存在非常多的 UseCase,随着业务的增加,对他们的管理将难以为继。
因此,UseCase 需要一个有效的手段来进行管理,首先,应当按功能对他们的包名进行划分。同一个业务的 UseCase 最好具备相同的包名。
其次,我们不能陷入所有业务都用 UseCase 的极端情况中,很多时候,我们可以将一些极度类似的功能组织在一个类中,其中提供多个公开的方法,这样的写法在以前很常见,比如各种 Manager, Helper, Resolver 等,他们能有效的减少 UseCase 数量,并且相对简单。
UiState
UiState 是用来描述当前 UI 状态的集合类,一般来说应该是个 data class。
UiState 一定是不可变类,如果希望更改其中的某个值,应当重新创建一个对象,直接通过 data class 提供的 copy 方法即可,例如:
data class LoginUiState(val name:String,val avatar: String,val consentAgreed: Boolean
)fun onAgreeChecked(){uistate = uiState.copy(consentAgreed = true,)
}
UiState 中的数据应当尽可能的方便给 UI 直接使用,因为 UiState 本身就是为了 UI 设计的,例如对于一个需要显示的格式化后的时间,格式化的逻辑最好放在 ViewModel 或者更内层,而不是直接给 UiState 一个时间戳,让 UI 层去格式化。很多时候看起来简单的逻辑也可能犯错误,UI 层没有能力处理异常。
在 ViewModel 中如果需要更新 UiState,可以直接通过 update
方法。
_uiState.update {it.copy(name = "zhangke")
}
ViewModel
ViewModel 负责管理 UI 状态,执行对应的业务逻辑。
因此 ViewModel 的生命周期与页面是一致的。
一般来说我们会通过直接使用 Jetpack 提供的 ViewModel,但也可以自己创建其他类型的 ViewModel,只要控制好生命周期即可。
ViewModel 主要负责两件事情:
- 对外提供当前 UI 状态
- 接收 UI 事件并作出响应
当前 UI 状态我们通过将 UiState 包装在 StateFlow 里对外提供。
private val _uiState = MutableStateFlow()
val uiState:StateFlow<UserUiState> = _uiState.asStateFlow()
接受 UI 事件这点需要注意,ViewModel 需要做的是接收 UI 事件,例如用户手势输入,至于用户点击之后要做什么事情这是 ViewModel 的内部逻辑,不应该对外暴露。
UI 层
我们这里说的 UI 层就是指一个页面,除了常规的 Activity/Fragment 之外,对于 Compose 来说一个页面可能对应的是一个 Composable 函数,这取决于 UI 层的实现。
UI 层应该完全是数据驱动的,UI 层的作用就是百分之百的将 UiState 渲染出来,UiState 发生变化,UI 也跟着变化,这一点声明式 UI 框架做的很好。
UI 层虽然也可以处理一些简单的事件,但大部分的事件都还是要交给 ViewModel 来处理。
以上就是 Android 整洁架构中的一些关键概念的介绍,我已经按照这个架构开发了一年多了,目前看下来确实会让架构很整洁,但对于一些复杂的业务场景,尤其是可能需要穿透多个层级,跨越常规生命周期的模块就需要更精细的设计了。
相关文章:

Clean 架构下的现代 Android 架构指南
Clean 架构下的现代 Android 架构指南 Clean 架构是 Uncle Bob 提出的一种软件架构,Bob 大叔同时也是 SOLID 原则的命名者。 Clean 架构图如下: 这张图描述的是整个软件系统的架构,而不是单体软件,其中至少包括服务端以及客户端…...

代码随想录算法训练营第四十六天| 139 单词拆分
目录 139 单词拆分 139 单词拆分 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool>dp(s.size() 1);//长度为i的字符串时能否成功拆分unordered_set<string>set(wordDict.begin(),wordDict.end());dp[0] t…...

IEEE期刊论文模板
一、模板下载 1、登陆IEEE作者中心Author Center 地址:Publish with IEEE Journals - IEEE Author Center Journals 2、点击“Download a template” 3、在弹出的模板下载页面点击IEEE模板选择器“IEEE Template Selector” 4、在弹出的模板选择器页面点击“Tran…...

上位机与PLC:ModbusTCP通讯之数据类型转换
前请提要: 从PLC读取的数值,不管是读正负整数还是正负浮点数,读取过来后都会变成UInt16,也就是Ushort类型 一、ushort(UInt16)转成 Int32 源代码方法: //ushort类型转Int32类型的方法private int ushortToInt32(ushort[] date, int start){//先进行判断,长度是否正确…...

界面控件DevExpress WPF导航组件,助力升级应用程序用户体验!(上)
DevExpress WPF的Side Navigation(侧边导航)、TreeView、导航面板组件能帮助开发者在WPF项目中添加Windows样式的资源管理器栏或Outlook NavBar(导航栏),DevExpress WPF NavBar和Accordion控件包含了许多开发人员友好的…...

联合基于信息论的安全和隐蔽通信的框架
这个标题很帅 abstractintroductionsystem modelPROPOSED JOINT OPTIMIZATION OF ITS AND COVERT TRANSMISSION RATE信息论安全 (ITS)隐蔽通信需要(CC)Joint Information-Theoretic Secrecy and Covert Communication in the Presence of an Untrusted User and Warden 202…...

行业地位失守,业绩持续失速,科沃斯的故事不好讲
特劳特曾在《定位》一书中提到,为了在容量有限的消费者心智中占据品类,品牌最好的差异化就是成为第一,做品类领导者或开创者,销量遥遥领先;其次分化品类,做到细分品类的唯一,即细分品类的第一。…...

蓝桥杯:货物摆放--因数存到数组里的技巧--减少运算量的方法
小蓝有一个超大的仓库,可以摆放很多货物。 现在,小蓝有 n 箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所有的货物最终摆成一个大…...

我的创作纪念日——一年
机缘 初心始于对技术的热爱和分享知识的渴望。最初,我在一次练习中遇到了一些问题,通过解决这些问题并将解决方案记录下来,我意识到分享经验对自己和他人都非常有价值。于是,我开始在博客和社交平台上记录日常学习过程、撰写技术…...

Windows server 部署iSCSI共享磁盘搭建故障转移群集
在域环境下,在域控制器中配置iSCSI服务,配置共享网络磁盘,在节点服务器使用共享磁盘,并在节点服务器中搭建故障转移群集,实现故障转移 环境准备 准备3台服务器,配置都是8g2核,50g硬盘…...

2023年山东省职业院校技能大赛信息安全管理与评估二三阶段样题
2023年山东省职业院校技能大赛信息安全管理与评估二三阶段 样题 第二阶段 模块二 网络安全事件响应、数字取证调查、应用程序安全 一、竞赛内容 Geek极安云科专注技能竞赛技术提升,基于各大赛项提供全面的系统性培训,拥有完整的培训体系。团队拥有曾…...

数据结构——栈与栈排序
栈的特性 栈是一种遵循后进先出(LIFO)原则的数据结构。其基本操作包括: push:将元素添加到栈顶。pop:移除栈顶元素。peek:查看栈顶元素,但不移除。 栈排序的原理 栈排序的核心是使用两个栈&…...

Java Web应用小案例 - 实现用户登录功能
文章目录 一、使用纯JSP方式实现用户登录功能(一)项目概述(二)实现步骤1、创建Web项目2、创建登录页面 二、使用JSPServlet方式实现用户登录功能三、使用JSPServletDB方式实现用户登录功能 一、使用纯JSP方式实现用户登录功能 &a…...

Excel——多列合并成一列的4种方法
Excel怎么将多列内容合并成一列? 怎么将多个单元格的内容连接起来放在一个单元格里? 比如下图,要将B、C、D列的内容,合并成E列那样,该怎么做呢? △图1 本文中,高潜老师将给大家介绍 4种 将多…...

Vue笔记(四)路由
路由(Vue Router) 用Vue Vue Router创建单页面应用非常简单。当加入Vue Router时,需要将组件映射到路由上,让Vue知道在哪里渲染它们。 路由基本例子 <!-- 引入Vue 和 router --><script src"https://unpkg.com/vu…...

Redis部署-哨兵模式
目录 redis sentinel相关名词 redis sentinel架构 故障转移流程 基于docker搭建redis哨兵 准备工作 搭建过程 模拟主节点宕机,观察哨兵节点的工作流程 哨兵重新选取主节点的流程 1.主观下线 2.客观下线 3.哨兵节点推举出一个leader节点 4.leader选举完毕,leader挑选…...

滑动窗口练习(三)— 加油站问题
题目 测试链接 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组…...

udemy angular decoration 自存
番外 为什么一个ts文件变成了component,因为它使用了components装饰器 components is just a class,you export it so angular know how to use it 举例:组件装饰器 decoration前总是有一个符号 decoration的作用(之一?) NgModu…...

msvcr90.dll丢失的解决方法分享,5个快速修复dll文件丢失教程
在今天的电脑使用过程中,我们可能会遇到各种各样的问题。其中之一就是msvcr90.dll丢失的问题。那么,msvcr90.dll是什么?msvcr90.dll丢失对电脑有什么影响?又该如何解决这个问题呢?接下来,我将为大家详细介绍…...

海外媒体发稿:软文发稿推广技巧解析超级实用-华媒舍
随着互联网时代的发展,软文发稿成为推广产品与服务的重要手段之一。本文将向大家介绍软文发稿推广的技巧,帮助您更好地利用软文推广商业活动。无论是拥有自己的品牌还是个人创业者,都可以从中受益。 1. 什么是软文? 软文是指以文…...

vm net 方式 静态ip配置访问主机IP和外网
1、win 11 安装vm,镜像文件 F:\software\VMwork\CentOS-7-x86_64-Everything-1804.iso 2、配置网络 net 方式 3、右击网络--》属性---》更改适配器设置--》vmnet8 属性、这里不做配置会出现主机ping通访问不通的情况,(访问不通,…...

Vue笔记(二)基本语法
基本语法 <style> table {border-collapse: collapse;margin:0 auto; } strong {color: rgb(235, 51, 100); }td, th {padding-left: 6px; } table tr td:first-child {width:150px } table tr td:nth-child(2) {width:300px } </style> <template><tabl…...

前端面试提问(4)
1、手撕防抖与节流、树与对象的转换、递归调用,链表头插法 1.1、防抖 防抖函数用于延迟执行某个函数,直到过了一定的间隔时间(例如等待用户停止输入)后再执行。 即后一次点击事件发生时间距离一次点击事件至少间隔一定时间。 …...

基于BEV+Transformer的地面要素感知+建模技术在高德的应用
导读 本文将主要介绍BEVTransformer端到端感知与建模技术在高德各项业务中的应用,如高精地图中地面要素(包含线要素和地面标识)自动化上的具体方案及其演化过程。该方案使用BEVTransformer技术来实现采集车上不同传感器(包含激光和…...

了解c++11中的新增
一,统一的初始化列表 在引入c11后,我们得出计划都可以用初始化列表进行初始化。 C11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围,使其可用于所有的内置类型和用户自 定义的类型, 使用初始化列表时,可添加等…...

104. 二叉树的最大深度(Java)
目录 解法: 官方解答: 方法一:深度优先搜索 方法二:广度优先搜索 思路与算法 复杂度分析 时间复杂度: 空间复杂度: 给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根…...

SpringSecurity6 | 自定义认证规则
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏…...

浅析安科瑞电动机保护器在广州某地铁项目的设计与应用-安科瑞 蒋静
1 摘要 随着城市的发展,较多城市的轨道交通选择修建地下式车辆段(或停车场),即车辆段(或停车场)位于地下或设置有上盖(上盖上再做物业开发)。为了给工作人员提供良好的工作环境、给…...

LeetCode 2048. 下一个更大的数值平衡数
【LetMeFly】2048.下一个更大的数值平衡数 力扣题目链接:https://leetcode.cn/problems/next-greater-numerically-balanced-number/ 如果整数 x 满足:对于每个数位 d ,这个数位 恰好 在 x 中出现 d 次。那么整数 x 就是一个 数值平衡数 。…...

多线程(初阶七:阻塞队列和生产者消费者模型)
目录 一、阻塞队列的简单介绍 二、生产者消费者模型 1、举个栗子: 2、引入生产者消费者模型的意义: (1)解耦合 (2)削峰填谷 三、模拟实现阻塞队列 1、阻塞队列的简单介绍 2、实现阻塞队列 &#…...