- Published on
什么是依赖注入
- Authors
- Name
依赖注入(Dependency Injection,简称 DI)是一个很常见、也很容易被讲复杂的概念。
先说最直白的版本:
一个对象不要自己去创建它依赖的对象,而是由外部把依赖传进来。
这就是依赖注入。
什么是“依赖”
如果一个类型在工作时需要另一个类型的帮助,那后者就是它的依赖。
例如:
UserService需要数据库对象ArticleViewModel需要网络请求客户端Logger需要输出目标
这些被依赖的对象,如果都在类内部直接 new / init 出来,就会让代码耦合得很紧。
什么是依赖注入
依赖注入的核心是:
不要在对象内部直接创建依赖,而是把依赖从外部传进来。
它通常和 IoC(控制反转)一起出现。
可以把 IoC 理解成更大的思想,而 DI 是其中一种具体实现方式。
通常,依赖注入可以通过以下方式实现:
- 构造函数注入:通过初始化方法传递依赖
- Setter 注入:通过属性或方法设置依赖
- 接口注入:通过约定的接口暴露注入入口
其中最常见、也最推荐入门时理解的是构造函数注入。
它解决了什么问题
依赖注入主要解决以下问题:
- 降低耦合 业务对象依赖抽象,而不是写死某个具体实现
- 便于测试 测试时可以注入 mock 或 fake 对象
- 更容易替换实现 例如把本地缓存替换成远程缓存时,业务代码不需要大改
- 更容易扩展 依赖关系更清晰,代码也更容易维护
为什么叫“注入”
这个名字其实很直白:
依赖:对象运行时需要的外部能力注入:这些能力不是它自己创建的,而是由外部传进来的
也就是说,重点不在“注入”这个动作有多神秘,而在于:
依赖的创建权不在对象自己手里。
一个 Swift 示例
下面用构造函数注入举一个很常见的例子。
假设 UserService 需要把用户数据保存到数据库中。
// 定义一个协议,抽象数据库连接的行为
protocol DatabaseConnection {
func save(user: String)
}
// 具体的数据库实现
class MySQLDatabaseConnection: DatabaseConnection {
func save(user: String) {
print("Saving \(user) to MySQL database")
}
}
// UserService 使用依赖注入
class UserService {
private let dbConnection: DatabaseConnection
// 构造函数注入
init(dbConnection: DatabaseConnection) {
self.dbConnection = dbConnection
}
func saveUser(_ user: String) {
dbConnection.save(user: user)
}
}
// 使用示例
let mysqlConnection = MySQLDatabaseConnection()
let userService = UserService(dbConnection: mysqlConnection)
userService.saveUser("Alice") // 输出: Saving Alice to MySQL database
这个例子里最关键的一点是:UserService 并不知道自己拿到的到底是 MySQL、PostgreSQL,还是测试用的假对象。
它只依赖 DatabaseConnection 这个抽象协议。
更换实现时更容易
class PostgreSQLDatabaseConnection: DatabaseConnection {
func save(user: String) {
print("Saving \(user) to PostgreSQL database")
}
}
let postgresConnection = PostgreSQLDatabaseConnection()
let userService = UserService(dbConnection: postgresConnection)
userService.saveUser("Bob") // 输出: Saving Bob to PostgreSQL database
单元测试也更容易写
class MockDatabaseConnection: DatabaseConnection {
var savedUser: String?
func save(user: String) {
savedUser = user
print("Mock saving \(user)")
}
}
// 测试代码
let mockConnection = MockDatabaseConnection()
let userService = UserService(dbConnection: mockConnection)
userService.saveUser("Charlie")
print(mockConnection.savedUser ?? "None") // 输出: Charlie
小结
如果要把依赖注入压缩成一句话,就是:
让对象依赖抽象,并由外部提供具体实现。
这样做最大的价值不是“写法高级”,而是让代码:
- 更松耦合
- 更容易测试
- 更容易替换实现
- 更适合长期维护