Published on

什么是依赖注入

Authors
  • Name
    Twitter

依赖注入(Dependency Injection,简称 DI)是一个很常见、也很容易被讲复杂的概念。

先说最直白的版本:

一个对象不要自己去创建它依赖的对象,而是由外部把依赖传进来。

这就是依赖注入。

什么是“依赖”

如果一个类型在工作时需要另一个类型的帮助,那后者就是它的依赖。
例如:

  • UserService 需要数据库对象
  • ArticleViewModel 需要网络请求客户端
  • Logger 需要输出目标

这些被依赖的对象,如果都在类内部直接 new / init 出来,就会让代码耦合得很紧。

什么是依赖注入

依赖注入的核心是:
不要在对象内部直接创建依赖,而是把依赖从外部传进来。

它通常和 IoC(控制反转)一起出现。
可以把 IoC 理解成更大的思想,而 DI 是其中一种具体实现方式。

通常,依赖注入可以通过以下方式实现:

  1. 构造函数注入:通过初始化方法传递依赖
  2. Setter 注入:通过属性或方法设置依赖
  3. 接口注入:通过约定的接口暴露注入入口

其中最常见、也最推荐入门时理解的是构造函数注入。

它解决了什么问题

依赖注入主要解决以下问题:

  • 降低耦合 业务对象依赖抽象,而不是写死某个具体实现
  • 便于测试 测试时可以注入 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

小结

如果要把依赖注入压缩成一句话,就是:

让对象依赖抽象,并由外部提供具体实现。

这样做最大的价值不是“写法高级”,而是让代码:

  • 更松耦合
  • 更容易测试
  • 更容易替换实现
  • 更适合长期维护