当涉及到使代码更加可测试时,依赖注入是一个重要工具。与其让对象创建自己的依赖关系或作为单例访问它们,不如让对象在工作中需要的一切都从外部传入。这使我们更容易看到一个给定的对象有哪些确切的依赖关系,同时也使测试变得更加简单——因为可以模拟依赖项以捕获和验证状态和值。
然而,尽管它很有用,但如果在一个项目中广泛使用,依赖注入也会成为一个相当大的痛点。随着一个给定对象的依赖数量的增加,初始化它可能成为一个相当麻烦的事情。让代码可测试是件好事,但如果要以这样的初始化器为代价,那就太糟糕了:
代码语言:javascript复制class UserManager {
init(dataLoader: DataLoader, database: Database, cache: Cache,
keychain: Keychain, tokenManager: TokenManager) {
...
}
}
本周,让我们来看看一种依赖注入技术,它可以让我们实现可测试性,而不强迫我们写这种大规模的初始化器或复杂的依赖管理代码。
传递依赖关系
在使用依赖注入时,我们经常会出现上述情况,主要原因是我们需要传递依赖关系,以便以后使用它们。例如,假设我们正在构建一个消息应用程序,我们有一个视图控制器来显示用户的所有消息:
代码语言:javascript复制class MessageListViewController: UITableViewController {
private let loader: MessageLoader
init(loader: MessageLoader) {
self.loader = loader
super.init(nibName: nil, bundle: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loader.load { [weak self] messages in
self?.reloadTableView(with: messages)
}
}
}
正如你所看到的,我们将一个MessageLoader
注入到MessageListViewController
中,然后用它来加载数据。这还不算太糟,因为我们只有一个依赖关系。然而,我们的列表视图很可能不是只有一层,这在某种程度上需要我们实现导航到另一个视图控制器。
假设我们想让用户在点击消息列表中的某个单元格时,能够导航到一个新的视图。对于这个新的视图,我们创建了一个MessageViewController
,它既可以让用户查看消息的全文,也可以对其进行回复。为了启用回复功能,我们实现了一个MessageSender
类,在创建新的视图控制器时,我们将其注入到新的视图控制器中,像这样:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let message = messages[indexPath.row]
let viewController = MessageViewController(message: message, sender: sender)
navigationController?.pushViewController(viewController, animated: true)
}
问题来了。由于MessageViewController
需要一个MessageSender
的实例,我们也需要让MessageListViewController
知道这个类。一个选择是简单地将发送者也添加到列表视图控制器的初始化器中:
class MessageListViewController: UITableViewController {
init(loader: MessageLoader, sender: MessageSender) {
...
}
}
虽然上面的方法可行,但它开始把我们引向另一个庞大的初始化器,并使MessageListViewController
变得更难使用(也相当令人困惑,为什么列表首先需要知道发件人?