设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间12 现在时间12 缓存数据 愿你迷路一生最后还是走到我身旁?

愿你迷路一生最后还是走到我身旁? -- 10.青涩

查看: 1430|回复: 2

详解Android官方架构中UseCase

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:232
  • 打卡月天数:0
  • 打卡总奖励:3503
  • 最近打卡:2025-06-19 21:05:11
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
390
主题
360
精华
0
金钱
4692
积分
817
注册时间
2023-1-5
最后登录
2025-6-19

发表于 2023-7-21 01:44:57 来自手机 | 显示全部楼层 |阅读模式
1. UseCase 的用途

Android 最新的架构规范中,引入了 Domain Layer(译为领域层or网域层),建议大家使用 UseCase 来封装一些复杂的业务逻辑。
传统的 MVVM 架构中,我们习惯用 ViewModel 来承载业务逻辑,随着业务规模的扩大,ViewModel 变得越来越肥大,职责不清。
024543n9bp9p69kkr6tlgk.png

Clean Architecture 提出的关注点分离和单一职责(SRP)的设计原则被广泛认可,因此 Android 在最新架构中引入了 Clean Architecture 中 UseCase 的概念。ViewModel 归属 UI Layer,更加聚焦 UiState 的管理,UI 无关的业务逻辑下沉 UseCase,UseCase 与 ViewModel 解耦后,也可以跨 ViewModel 提供公共逻辑。
Android 架构早期的示例代码 todo-app 中曾经引入过 UseCase 的概念,最新架构中只不过是将 UseCase 的思想更明确了,最新的 UseCase 示例可以从官方的 NIA 中学习。
NIA: github.com/android/now…

2. UseCase 的特点

官方文档认为 UseCase 应该具有以下几个特点:

2.1 不持有状态

可以定义自己的数据结构类型,但是不能持有状态实例,像一个纯函数一样工作。甚至直接推荐大家将逻辑重写到 invoke 方法中,像调用函数一样调用实例。
下面是 NIA 中的一个示例:
  1. GetRecentSearchQueriesUseCase
复制代码

024544bxvsqxd7sz7d7usk.jpeg


2.2 单一职责

严格遵守单一职责,一个 UseCase 只做一件事情,甚至其命名就是一个具体行为。扫一眼 UseCase 的文件目录大概就知道 App 的大概功能了。
下面 NIA 中所有 UseCases:
024544t14a0gaa616iii4i.jpeg


2.3 可有可无

官方文档中将 UseCase 定义为可选的角色,按需定义。简单的业务场景中允许 UI 直接访问 Repository。如果我们将 UseCase 作为 UI 与 Data 隔离的角色,那么工程中会出现很多没有太大价值的 UseCase ,可能就只有一行调用 Repoitory 的代码。

3. 如何定义 UseCase

如上所述,官方文档虽然对 UseCase 给出了一些基本定义,但是毕竟是一个新新生概念,很多人在真正去写代码的时候仍然会感觉不清晰,缺少有效指引。在究竟如何定义 UseCase 这个问题上,还有待大家更广泛的讨论,形成可参考的共识。本文也是带着这个目的而生,算是抛砖引玉吧。

3.1 Optional or Mandatory?

首先,官方文档认为 UseCase 是可选的,虽然其初衷是好的,大家都不希望出现太多 One-Liner 的 UseCase,但是作为一个架构规范切忌模棱两可,这种“可有可无”的规则其结局往往就是“无”。
业务刚起步时由于比较简单往往定义在 Repository 中,随着业务规模的扩大,应该适当得增加 UseCase 封装一些复杂的业务逻辑,但是实际项目中此时的重构成本会让开发者变得“懒惰”,UseCase 最终难产。
那放弃 UseCase 呢?这可能会造成 Repository 的职责不清和无限膨胀,而且 Repository 往往不止有一个方法, ViewModel 直接依赖 Repository 也违反了 SOLID 中的另一个重要原则 ISP ,ViewModel 会因为不相关的 Repository 改动导致重新编译。
ISP(Interface Segregation Principle,接口隔离原则) 要求将接口分离成更小的和更具体的接口,以便调用方只需知道其需要使用的方法。这可以提高代码的灵活性和可重用性,并减少代码的依赖性和耦合性。
为了降低前期判断成本和后续重构成本,如果我们有业务持续壮大的预期,那不妨考虑将 UseCase 作为强制选项。当然,最好这需要研究如何降低 UseCase 带来的模板代码。

3.2 Class or Object?

官方建议使用
  1. Class
复制代码
定义 UseCase,每次使用都实例化一个新对象,这会做成一些重复开销,那么可否用
  1. object
复制代码
定义 UseCase 呢?
UseCase 理论上可以作为单例存在,但 Class 相对于 Object 有以下两个优势:

  • UseCase 希望像纯函数一样工作,普通 Class 可以确保每次使用时都会创建一个新的实例,从而避免状态共享和副作用等问题。
  • 普通类可以通过构造参数注入不同的 Repository,UseCase 更利于复用和单元测试
如果我们强烈希望 UseCase 有更长的生命周期,那借助 DI 框架,普通类也可以简单的支持。例如 Dagger 中只要添加
  1. @Singleton
复制代码
注解即可
  1. @Singleton
  2. class GetRecentSearchQueriesUseCase @Inject constructor(
  3.     private val recentSearchRepository: RecentSearchRepository,
  4. ) {
  5.     operator fun invoke(limit: Int = 10): Flow<List<RecentSearchQuery>> =
  6.         recentSearchRepository.getRecentSearchQueries(limit)
  7. }
复制代码
3.3 Class or Function?

既然我们想像函数一样使用 UseCase ,那为什么不直接定义成
  1. Function
复制代码
呢?比如像下面这样
  1. fun GetRecentSearchQueriesUseCase : Flow<List<RecentSearchQuery>>
复制代码
这确实遵循了 FP 的原则,但又丧失了 OOP 封装性的优势:

  • UseCase 往往需要依赖 Repository 对象,一个 UseCase Class 可以将 Repository 封装为成员存储。而一个 UseCase Function 则需要调用方通过参数传入,使用成本高不说,如果 UseCase 依赖的 Repository 的类型或者数量发生变化了,调用方需要跟着修改
  • 函数起不到隔离 UI 和 Data 的作用,ViewModel 仍然需要直接依赖 Repository,为 UseCase 传参
  • UseCase Class 可以定义一些 private 的方法,相对于 Function 更能胜任一些复杂逻辑的实现
可见,在 UseCase 的定义上 Function 没法取代 Class。当然 Class 也带来一些弊端:

  • 暴露多个方法,破坏 SRP 原则。所以官方推荐用
    1. verb in present tense + noun/what (optional) + UseCase
    复制代码
    动词命名,也是想让职责更清晰。
  • 携带可变状态,这是大家写 OOP 的惯性思维
  • 样板代码多

3.4 Function interface ?

通过前面的分析我们知道:UseCase 的定义需要兼具 FP 和 OOP 的优势。这让我想到了 Function(SAM) Interface 。Function Interface 是一个单方法的接口,可以低成本创建一个匿名类对象,确保对象只能有一个方法,同时具有一定封装性,可以通过“闭包”依赖 Repository。此外,Kotlin 对 SAM 提供了简化写法,一定程度也减少了样板代码。
Functional (SAM) interfaces: kotlinlang.org/docs/fun-in…
改用 Function interface 定义 GetRecentSearchQueriesUseCase 的代码如下:
  1. fun interface GetRecentSearchQueriesUseCase : () -> Flow<List<RecentSearchQuery>>
复制代码
用它创建 UseCase 实例的同时,实现函数中的逻辑
  1. val recentSearchQueriesUseCase = GetRecentSearchQueriesUseCase {
  2.     //...
  3. }
复制代码
我在函数实现中如何 Repository 呢?这要靠 DI 容器获取。官方示例代码中都使用 Hilt 来解耦 ViewModel 与 UseCase 的,ViewModel 不关心 UseCase 的创建细节。下面是 NIA 的代码, GetRecentSearchQueriesUseCase 被自动注入到
  1. SearchViewModel
复制代码
中。
  1. @HiltViewModel
  2. class SearchViewModel @Inject constructor(
  3.     recentSearchQueriesUseCase: GetRecentSearchQueriesUseCase // UseCase 注入 VM
  4.     //...
  5. ) : ViewModel() {
  6.     //...
  7. }
复制代码
Function interface 的 GetRecentSearchQueriesUseCase 没有构造函数,需要通过 Dagger 的
  1. @Module
复制代码
安装到 DI 容器中,
  1. provideGetRecentSearchQueriesUseCase
复制代码
参数中的 RecentSearchRepository 可以从容器中自动获取使用。
  1. @Module
  2. @InstallIn(ActivityComponent::class)
  3. object UseCaseModule {
  4.     @Provides
  5.     fun provideGetRecentSearchQueriesUseCase(recentSearchRepository: RecentSearchRepository) =
  6.         GetRecentSearchQueriesUseCase { limit ->
  7.             recentSearchRepository.getRecentSearchQueries(limit)
  8.         }
  9. }
复制代码
当时用 Koin 作为 DI 容器时也没问题,代码如下:
  1. single<GetRecentSearchQueriesUseCase> {
  2.     GetRecentSearchQueriesUseCase { limit ->
  3.        recentSearchRepository.getRecentSearchQueries(limit)
  4.     }
  5. }
复制代码
4. 总结

UseCase 作为官方架构中的新概念,尚没有完全深入人心,需要不断探索合理的使用方式,本文给出一些基本思考:

  • 考虑到架构的扩展性,推荐在 ViewModel 与 Repository 之间强制引入 UseCase,即使眼下的业务逻辑并不复杂
  • UseCase 不持有可变状态但依赖 Repository,需要兼具 FP 与 OOP 的特性,更适合用 Class 定义而非 Function
  • 在引入 UseCase 之前应该先引入 DI 框架,确保 ViewModel 与 UseCase 的耦合。
  • Function Interface 是 Class 之外的另一种定义 UseCase 的方式,有利于代码更加函数式
以上就是详解Android官方架构中UseCase的详细内容,更多关于Android官方架构UseCase 的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-28
最后登录
2022-12-28

发表于 2024-4-6 21:38:01 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
13
积分
6
注册时间
2023-9-15
最后登录
2023-9-15

发表于 2025-4-6 09:56:13 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表