Android 架构进阶:为什么项目越大,越需要把对象创建权拿走

作者:潜龙勿用之化骨龙日期:2026/8/20

刚开始写 Android 项目的时候,我其实不太理解 DI 有什么必要。

一个对象而已:

1val repository = UserRepositoryImpl()
2

直接创建不就完了吗?

如果只是一个小项目,确实没什么问题。

甚至我觉得,这时候为了 HiltKoin 再引入一套依赖注入体系,反而有点重。

真正让我改变想法的,是项目开始变大以后。

你会发现,项目变复杂以后,麻烦来了:

到处都是创建对象。


一、项目小的时候,直接创建对象没有问题

比如:

1class UserRepository {
2
3    fun getUser() {
4        // ...
5    }
6}
7

使用的时候:

1class UserViewModel {
2
3    private val repository = UserRepository()
4
5    fun loadUser() {
6        repository.getUser()
7    }
8}
9

完全没问题。

因为这个时候:

  • 依赖很少
  • 实现很明确
  • 生命周期简单
  • 对象可能只有一个地方使用

所以:

1UserRepository()
2

本身不是问题。

真正的问题,是随着项目变大以后,new 开始出现在越来越多的地方。


二、真正麻烦的是:业务代码开始负责“组装对象”

假设登录功能一开始很简单:

1class LoginServiceImpl(
2    private val repository: UserRepository
3)
4

创建:

1val service = LoginServiceImpl(repository)
2

没什么问题。

后来需求慢慢增加。

登录需要配置:

1class LoginConfig
2

需要安全组件:

1class SecurityManager
2

最后可能变成:

1class LoginServiceImpl(
2    private val repository: UserRepository,
3    private val config: LoginConfig,
4    private val logger: Logger,
5    private val securityManager: SecurityManager
6)
7

于是创建它的人也开始变复杂:

1val service = LoginServiceImpl(
2    repository,
3    config,
4    logger,
5    securityManager
6)
7

这时候真正的问题出现了。

谁在创建 LoginServiceImpl,谁就必须知道它依赖什么。

也就是说:

1LoginViewModel
2      
3知道 LoginServiceImpl
4      
5知道它需要 Repository
6      
7知道它需要 Config
8      
9知道它需要 SecurityManager
10

业务代码开始了解越来越多的基础设施。

这才是麻烦的开始。


三、对象创建本身不复杂,复杂的是依赖关系

很多人第一次接触 DI,会把它理解成:

Hilt 帮我创建对象。

这个理解太浅了。

因为创建一个对象真的很简单:

1UserRepositoryImpl()
2

真正复杂的是:

1UserViewModel
2      
3LoginUseCase
4      
5LoginRepository
6      
7UserApi
8      
9Retrofit
10      
11OkHttp
12

这已经不是“创建一个对象”了。

这是一整张对象依赖图

而项目继续变大以后,这张图可能变成:

1                    Retrofit
2                       
3                    UserApi
4                       
5                UserRepository
6                          
7             Database      Cache
8                            
9                 Room      DataStore
10
11
12UserRepository
13       
14   LoginUseCase
15       
16  LoginViewModel
17       
18       UI
19

真正难管理的是这张图。


四、如果每个业务类都可以创建对象,依赖关系迟早会失控

比如:

1class LoginViewModel {
2
3    private val api =
4        Retrofit.Builder()
5            .baseUrl("...")
6            .build()
7            .create(UserApi::class.java)
8}
9

一开始你可能觉得:

能跑就行。

但这样一来,LoginViewModel 已经知道了:

  • Retrofit
  • BaseUrl
  • UserApi
  • 网络配置

ViewModel 本来应该关心:

1用户点击登录
2        
3执行登录
4        
5展示结果
6

现在却开始关心:

1Retrofit 怎么初始化
2API 怎么创建
3BaseUrl 是什么
4

业务代码和基础设施绑在了一起。

以后你想把 Retrofit 换掉,就会发现:

不是不能改,而是改起来很别扭。


五、DI 真正做的事情,是把“决定权”拿走

所以 DI 真正变化的是:

LoginViewModel 不再决定 LoginService 是谁。

它只声明:

1class LoginViewModel @Inject constructor(
2    private val loginService: LoginService
3)
4

ViewModel 只关心一件事情:

我需要一个 LoginService

至于:

1到底是 LoginServiceImpl?
2
3还是 CacheLoginServiceImpl?
4
5还是 FakeLoginService?
6
7它需要哪些参数?
8
9这些参数从哪里来?
10
11生命周期是什么?
12
13

这些事情不再由 ViewModel 决定。


六、这就是 IoC:控制权发生了变化

没有 DI 的时候:

1业务代码
2   
3我要 LoginService
4   
5我自己创建
6   
7LoginServiceImpl
8   
9我自己解决它的依赖
10

业务代码拥有控制权。

而有 DI 以后:

1业务代码
2   
3声明:
4我需要 LoginService
5   
6DI
7   
8决定使用哪个实现
9   
10解决它的依赖
11   
12创建对象
13   
14注入业务代码
15

所以 IoC(Inversion of Control)真正“反转”的,并不是:

创建对象的方法。

而是:

谁拥有依赖关系的控制权。

以前是业务代码决定。

现在交给架构层决定。


七、项目越大,这件事情就越重要

因为项目越大,依赖关系越复杂。

小项目可能只有:

1ViewModel
2   
3Repository
4

直接创建完全没问题。

中型项目可能已经变成:

1ViewModel
2   
3UseCase
4   
5Repository
6   
7Api
8Database
9Cache
10Config
11Logger
12

大型项目可能还要考虑:

1不同实现
2不同环境
3不同模块
4不同生命周期
5不同配置
6

这时候如果每个业务类都可以决定:

我要创建哪个对象。

最后整个项目会出现一个很麻烦的情况:

依赖关系散落在业务代码的各个角落。

你根本不知道一个对象到底在哪里被创建。


八、真正值得关注的是“实现类变化”

比如现在:

1interface LoginService {
2
3    fun login()
4}
5

生产环境:

1class LoginServiceImpl : LoginService
2

测试环境:

1class FakeLoginService : LoginService
2

以后因为业务需求,又增加了缓存:

1class CacheLoginService : LoginService
2

如果业务代码直接依赖实现:

1val service = LoginServiceImpl()
2

那实现发生变化的时候,业务代码也要跟着变化。

但如果业务代码只依赖接口:

1class LoginViewModel(
2    private val service: LoginService
3)
4

那么:

1LoginViewModel
2       
3LoginService
4       
5       |
6 ┌─────┼────────────┐
7                  
8Impl  Fake       CacheImpl
9

实现怎么变化,业务代码都不需要知道。

这才是 DI 和抽象真正结合起来之后的价值。


九、构造参数变化,也是一个很现实的问题

比如最开始:

1class LoginServiceImpl(
2    private val repository: UserRepository
3)
4

创建:

1LoginServiceImpl(repository)
2

后来:

1class LoginServiceImpl(
2    private val repository: UserRepository,
3    private val config: LoginConfig
4)
5

再后来:

1class LoginServiceImpl(
2    private val repository: UserRepository,
3    private val config: LoginConfig,
4    private val logger: Logger
5)
6

如果这个对象在很多地方直接创建:

1LoginServiceImpl(...)
2

构造函数变化以后,所有创建点都可能受到影响。

而 DI 的意义就在这里。

让:

1LoginServiceImpl
2

的创建集中在依赖配置的位置。

例如 Hilt:

1@Module
2@InstallIn(SingletonComponent::class)
3object LoginModule {
4
5    @Provides
6    fun provideLoginService(
7        repository: UserRepository,
8        config: LoginConfig,
9        logger: Logger
10    ): LoginService {
11        return LoginServiceImpl(
12            repository,
13            config,
14            logger
15        )
16    }
17}
18

业务代码不需要跟着构造参数变化。

它依然只是:

1class LoginViewModel @Inject constructor(
2    private val loginService: LoginService
3)
4

十、所以 DI 管理的,其实是一张对象图

这也是我现在觉得理解 DI 最重要的一步。

不要只把 DI 理解成:

1@Inject
2    
3自动创建对象
4

应该把它理解成:

1                 UserApi
2                    
3                    |
4              UserRepository
5                    
6                    |
7                LoginUseCase
8                    
9                    |
10              LoginViewModel
11

DI 做的事情,是把这些依赖关系连接起来。

也就是:

1谁依赖谁
2      
3谁实现谁
4      
5谁负责创建
6      
7谁负责管理生命周期
8      
9不同环境使用什么实现
10

这些事情被集中管理以后,业务代码就可以干净很多。


十、什么时候应该开始考虑 DI?

我觉得可以看一个很简单的信号:

当一个业务类开始越来越关心“它的依赖怎么创建”时,就该考虑把创建权拿走了。

比如:

1class UserViewModel {
2
3    private val retrofit = ...
4    private val api = ...
5    private val database = ...
6    private val repository = ...
7}
8

如果一个 ViewModel 变成这样:

1既负责业务
2又负责创建 Retrofit
3又负责创建 Repository
4又负责管理配置
5又负责决定生命周期
6

那问题已经不是代码多了。

而是:

职责已经开始混在一起了。

这时候 DI 就有意义了。


最后、再回到最开始的问题

DI 是不是帮我们创建对象?

是。

但这只是最表面的一层。

真正值得理解的是:

1小项目
2
3UserRepository()
4        
5直接创建
6        
7没什么问题
8

随着项目变大:

1UserRepository
2       
3Api
4Database
5Cache
6Config
7Logger
8       
9依赖越来越多
10       
11对象创建越来越复杂
12       
13业务代码开始关心基础设施
14

这时候就需要把创建权拿走:

1业务代码
2    
3只声明依赖
4    
5接口 / 抽象
6    
7DI
8    
9决定实现
10    
11解决依赖
12    
13管理生命周期
14

所以我现在更愿意这样理解 DI:

DI 不是为了让你少写几行 new

它真正解决的是:当系统越来越复杂以后,谁来负责决定对象之间的依赖关系。

小项目里,自己创建对象没什么。

但当一个项目里开始出现几十、几百个对象,以及复杂的依赖关系时,如果每个业务类都拥有“创建依赖”的权力,系统很容易慢慢失控。

所以项目越大,越应该把这部分权力从业务代码里拿出来。

业务代码负责“我要什么”。

**架构负责“给你什么”。**有了DI,我们可以专注业务开发了。

Github Sample


Android 架构进阶:为什么项目越大,越需要把对象创建权拿走》 是转载文章,点击查看原文


相关推荐


Java对象头Mark Word状态与锁膨胀过程剖析
wuminyu2026/8/6

Java对象头Mark Word状态与锁膨胀过程剖析 前言对象头Mark Word状态与锁膨胀过程剖析1. 64位 HotSpot JVM 下 Mark Word 内存布局64位 JVM Mark Word 位结构图表C++ 头文件位常量定义 (`markOop.hpp`) 2. 状态机迁移全景与锁演进路线3. 偏向锁(Biased Locking)的加锁与撤销源码解析3.1 偏向锁的快速进入(Fast Path)3.2 偏向锁的撤销(Revocation) 4. 轻量级锁(Lig


2026 哪个远程控制软件好用?国内海外 6 款主流远程软件深度测评(附评分表与选型建议)
禁止默2026/7/28

2026 年了,远程控制软件早就不是"偶尔帮老妈修电脑"的低频工具了——居家连公司工位改方案、出差路上用平板回邮件、周末秒回 OA、被控端跑训练任务、远程串流玩 3A,这些场景已经把远程软件变成了天天开的生产力底座。 但"哪个好用"这个问题在 2026 年变得更难回答了,原因是市场出现了三条暗线: 国内三巨头分层明显:UU 远程靠网易的网络底子杀进来,把 4K/144fps、端口映射、远程终端全做成了免费;ToDesk 免费版开始卡时长;向日葵免费版广告越塞越多,并且开始排队收费。


让家里网速有据可查:用MySpeed做一个24小时测速看板
是店小二呀2026/7/20

前言 很多人办理宽带时看到的是500M、1000M,可真正用起来却不是那么回事。晚上看视频卡顿,家里多人同时上网就变慢,联系运营商时,对方一句“后台检测正常”,问题往往就很难继续推进。 问题不一定出在你不会测速,而是普通测速方式很难留下连续证据。偶尔打开第三方测速网站测一次,只能说明当时那一刻的结果,不能反映一天内不同时段的波动,也很难判断是不是晚高峰、Wi-Fi环境或线路质量造成的影响。 如果家里本来就有群晖NAS,可以把它变成一台长期在线的测速服务器。MySpeed就是适合这种场景的轻


PDF解析实现
马里马里奥-2026/7/12

1. 项目背景与需求分析 在现代Web应用中,PDF 文档处理是一个常见但复杂的需求。无论是企业 OA 系统、在线教育平台还是知识管理工具,都需要能够高效解析 PDF 内容。本次作业要求实现一个中间件,专门处理前端上传的PDF文件,具体要求如下: 输入格式:接收 Base64 编码的 PDF 数据核心功能:将 Base64 转换为正常 PDF 文件并提取文本内容输出要求:将提取的文本内容完整放入 system_message 中,作为后续对话的上下文扩展目标:最终版本应支持上传或发送包含完整


【Agent 学习日记】从问题到答案:RAG系统完整处理流程与核心机制深度拆解
小假是真的2026/7/4

目录 🍬前言 🍬一、 RAG 系统全流程总览(宏观视角) 🍬二、 离线预处理:决定 RAG 效果的上限 🍬三、 在线推理第一步:问题是如何被“拆解”的?(Query 拆解) 🍬四、 向量检索与重排序:从“大海捞针”到“精准定位” 🍬五、 大模型(LLM)在 RAG 中到底负责什么? 🍬六、 最终输出:不仅是“答案” 🍬七、 关键痛点与优化方向(总结展望) 🍬八、面试回答 🍡RAG完整处理流程 🍡问题如何拆解? 🍡大模型负责什么? 🍡最终输出什么


GitHub 热榜项目 - 周榜(2026-06-21)
CoderJia_2026/6/26

GitHub 热榜项目 - 周榜(2026-06-21) 生成于:2026-06-21 统计摘要 共发现热门项目: 21 个 Token赞助:siliconflow 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 本期热点趋势总结 本期 GitHub 热榜呈现出明显的 AI 工程化 与基础设施化 趋势:MCP 服务、Agent 技能、提示词安全扫描、RAG 压缩、代码知识图谱等项目集中爆发,说明开发重点已


《PyTorch 深度修炼》Dataset 和 DataLoader:数据如何喂给模型
闵孚龙2026/6/17

一、模型吃的不是文件,是 Batch Tensor 很多人刚学 PyTorch,会把数据加载理解成“读文件”。这个理解太浅。 训练模型时,真正进入模型的不是图片路径,不是 JSON,不是数据库记录,而是整理好的 Batch Tensor。 Dataset 负责回答一个问题:一个样本怎么取。DataLoader 负责回答另一个问题:怎样高效、稳定、成批地把样本送到训练循环。 所以 DataLoader 不是一个普通 for 循环。它是一条数据流水线。它管顺序、管批次、管拼接、管多进程、管预


Java Spring Data JPA 实战指南:Repository 查询、分页与实体映射
唐青枫2026/6/10

简介 Spring Data JPA 是 Spring Data 家族里专门用来简化 JPA 开发的模块。 它不是一个新的 ORM 规范。 更准确地说: JPA 是规范 Hibernate 是常见实现 Spring Data JPA 是 Spring 对 JPA Repository 的封装 在 Spring Boot 项目里,常见调用链大致是: Controller | v Service | v Repository | v Spring Data JPA |


阿里云ECS部署YOLO教程
MR_Colorful2026/6/2

1、阿里云注册 在官网注册账号:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 2、ECS配置选择 3、在阿里云 Workbench里为Ubuntu 18/20/22/24安装XFCE桌面(不推荐在这个里面使用,不好用!) stesteps1、通过VNC连接实例 step2、更新软件包列表和已安装的包 sudo apt update && sudo apt upgrade -y step3、安装XFCE桌面环境 sudo apt install -y xfce4 xfc


HarmonyOS 鸿蒙PC平台三方库移植:使用 vcpkg 移植 libzen(ZenLib)
展菲2026/5/25

网罗开发 (小红书、快手、视频号同名)   大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。 图书作者:《ESP32-C3 物联网工程开发实战》 图书作者:《SwiftUI 入门,进阶与实战》 超级个体:COC上海社区主理人 特约讲师:大学讲师,谷歌亚马逊分享嘉宾 科技

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读