在Swift中使用工厂进行依赖注入

2022-03-30 10:44:33 浏览数 (1)

当涉及到使代码更加可测试时,依赖注入是一个重要工具。与其让对象创建自己的依赖关系或作为单例访问它们,不如让对象在工作中需要的一切都从外部传入。这使我们更容易看到一个给定的对象有哪些确切的依赖关系,同时也使测试变得更加简单——因为可以模拟依赖项以捕获和验证状态和值。

然而,尽管它很有用,但如果在一个项目中广泛使用,依赖注入也会成为一个相当大的痛点。随着一个给定对象的依赖数量的增加,初始化它可能成为一个相当麻烦的事情。让代码可测试是件好事,但如果要以这样的初始化器为代价,那就太糟糕了:

代码语言: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类,在创建新的视图控制器时,我们将其注入到新的视图控制器中,像这样:

代码语言:javascript复制
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知道这个类。一个选择是简单地将发送者也添加到列表视图控制器的初始化器中:

代码语言:javascript复制
class MessageListViewController: UITableViewController {
    init(loader: MessageLoader, sender: MessageSender) {
        ...
    }
}

虽然上面的方法可行,但它开始把我们引向另一个庞大的初始化器,并使MessageListViewController变得更难使用(也相当令人困惑,为什么列表首先需要知道发件人?

0 人点赞