依赖注入:它会让你的代码更难改变吗?
Dependency Injection: could it make your code harder to change down the line?
据我了解,基本的依赖注入意味着不是在 class 内部创建依赖,而是在外部创建它并将其作为参数传入。
假设您有一个 class 记录器,它执行一些操作然后写入日志,它依赖于 WriteToFile 对象。不是在 Logger class 内部创建 WriteToFile 对象,而是在外部创建它并在每次创建新的 Logger 实例时将其作为参数传入。
让我感到困惑的是,假设在您的代码中有 1,000 个地方创建了一个 Logger 对象,并想象由于某种原因您不再需要在 Logger 中使用 WriteToFile 对象 class...
如果没有 DI,您只需删除 Logger class 中创建 WriteToFile 对象并使用它的代码。这可能需要几秒钟。
但是使用 DI,您必须找到创建 Logger 对象的那 1,000 个位置,并删除创建 WriteToFile 对象并将其作为参数传入的代码。
这是正确的还是我遗漏了一些重要的东西?
应用 DI 并不一定意味着您将 classes 完全颠倒过来,这样 all a class 的依赖关系是从外面供应。这很容易导致无法维护的混乱。
这就是为什么从 DI 的角度来看,我们将依赖项分为两个不同的组:
- 稳定的依赖关系: 是 classes(和功能),其行为是确定性的,并且您永远不会期望必须替换、包装、装饰或拦截。
- 不稳定的依赖关系: 根据定义,所有不稳定的依赖关系都是不稳定的。这些 classes(和功能)的行为是不确定的(例如
Random.Next
、DateTime.Now
或调用数据库时),或者是您希望能够使用的代码库的一部分替换、包装、装饰或拦截。
稳定和易变的依赖关系实际上还有更多。可以找到这些概念的更详细描述 here。
从 DI 的角度来看,我们只对易失性依赖感兴趣。这些是我们希望隐藏在抽象背后并通过构造函数注入的依赖项。抽象和注入 Volatile Dependencies 提供了许多有趣的优势,例如:
- Flexibility/maintainability。例如通过依赖
ILogger
,您可以通过更改单行代码(或翻转配置开关)将日志写入数据库而不是文件。
- 可测试性:通过允许替换易失性依赖项,隔离测试单个 class 变得更加容易。
另一方面,稳定的依赖项不需要根据定义进行交换,与它们的紧密耦合不会妨碍可测试性,因为它们的行为是确定性的。
如果我们查看您的具体情况,您的记录器就是一个很好的易变依赖示例:
- 记录器通常是 class 您希望在类似模型的插件中替换的记录器;明天你可能想登录到数据库。
- 您的记录器写入磁盘;这是不确定的行为。
- 您可能想在测试时用伪造的实现替换记录器。
这意味着您系统中的大多数 class 不应该依赖于您的记录器 实现 ,而是依赖于日志记录 抽象.
如您所解释的,您的记录器 class 包含一个 WriteToFile
对象。要了解它是否应该使用 DI 从外部提供给记录器,意味着您需要找出 WriteToFile
从记录器的角度来看是否是易失性依赖项。这个 WriteToFile
对象可能是您的记录器 class 的固有部分。它们一起构成一个组件。 类 单个组件内通常期望紧密耦合。在这种情况下,从记录器的角度来看,WriteToFile
是一个稳定的依赖项。
在您确定 WriteToFile
从记录器的角度来看是一个稳定的依赖项之后,就没有必要将它隐藏在抽象后面并将其注入到记录器中。那只会引入开销而不会增加任何好处。
将 DI 应用于稳定的依赖关系“使您的代码更难更改”,而将 DI 应用于易变的依赖关系则使您的代码更易于维护。
据我了解,基本的依赖注入意味着不是在 class 内部创建依赖,而是在外部创建它并将其作为参数传入。
假设您有一个 class 记录器,它执行一些操作然后写入日志,它依赖于 WriteToFile 对象。不是在 Logger class 内部创建 WriteToFile 对象,而是在外部创建它并在每次创建新的 Logger 实例时将其作为参数传入。
让我感到困惑的是,假设在您的代码中有 1,000 个地方创建了一个 Logger 对象,并想象由于某种原因您不再需要在 Logger 中使用 WriteToFile 对象 class...
如果没有 DI,您只需删除 Logger class 中创建 WriteToFile 对象并使用它的代码。这可能需要几秒钟。
但是使用 DI,您必须找到创建 Logger 对象的那 1,000 个位置,并删除创建 WriteToFile 对象并将其作为参数传入的代码。
这是正确的还是我遗漏了一些重要的东西?
应用 DI 并不一定意味着您将 classes 完全颠倒过来,这样 all a class 的依赖关系是从外面供应。这很容易导致无法维护的混乱。
这就是为什么从 DI 的角度来看,我们将依赖项分为两个不同的组:
- 稳定的依赖关系: 是 classes(和功能),其行为是确定性的,并且您永远不会期望必须替换、包装、装饰或拦截。
- 不稳定的依赖关系: 根据定义,所有不稳定的依赖关系都是不稳定的。这些 classes(和功能)的行为是不确定的(例如
Random.Next
、DateTime.Now
或调用数据库时),或者是您希望能够使用的代码库的一部分替换、包装、装饰或拦截。
稳定和易变的依赖关系实际上还有更多。可以找到这些概念的更详细描述 here。
从 DI 的角度来看,我们只对易失性依赖感兴趣。这些是我们希望隐藏在抽象背后并通过构造函数注入的依赖项。抽象和注入 Volatile Dependencies 提供了许多有趣的优势,例如:
- Flexibility/maintainability。例如通过依赖
ILogger
,您可以通过更改单行代码(或翻转配置开关)将日志写入数据库而不是文件。 - 可测试性:通过允许替换易失性依赖项,隔离测试单个 class 变得更加容易。
另一方面,稳定的依赖项不需要根据定义进行交换,与它们的紧密耦合不会妨碍可测试性,因为它们的行为是确定性的。
如果我们查看您的具体情况,您的记录器就是一个很好的易变依赖示例:
- 记录器通常是 class 您希望在类似模型的插件中替换的记录器;明天你可能想登录到数据库。
- 您的记录器写入磁盘;这是不确定的行为。
- 您可能想在测试时用伪造的实现替换记录器。
这意味着您系统中的大多数 class 不应该依赖于您的记录器 实现 ,而是依赖于日志记录 抽象.
如您所解释的,您的记录器 class 包含一个 WriteToFile
对象。要了解它是否应该使用 DI 从外部提供给记录器,意味着您需要找出 WriteToFile
从记录器的角度来看是否是易失性依赖项。这个 WriteToFile
对象可能是您的记录器 class 的固有部分。它们一起构成一个组件。 类 单个组件内通常期望紧密耦合。在这种情况下,从记录器的角度来看,WriteToFile
是一个稳定的依赖项。
在您确定 WriteToFile
从记录器的角度来看是一个稳定的依赖项之后,就没有必要将它隐藏在抽象后面并将其注入到记录器中。那只会引入开销而不会增加任何好处。
将 DI 应用于稳定的依赖关系“使您的代码更难更改”,而将 DI 应用于易变的依赖关系则使您的代码更易于维护。