我最近一直在使用 StructureMap,并且非常享受这种体验。但是,我可以看到一个人很容易被接口所有东西所迷惑,并最终得到将大量接口引入其构造函数的类。尽管在使用依赖注入框架时这确实不是一个大问题,但它仍然觉得有些属性真的不需要仅仅为了接口而被接口出来。
您在哪里画出接口与仅向类添加属性的界限?
我最近一直在使用 StructureMap,并且非常享受这种体验。但是,我可以看到一个人很容易被接口所有东西所迷惑,并最终得到将大量接口引入其构造函数的类。尽管在使用依赖注入框架时这确实不是一个大问题,但它仍然觉得有些属性真的不需要仅仅为了接口而被接口出来。
您在哪里画出接口与仅向类添加属性的界限?
依赖注入的主要问题在于,虽然它看起来像是松散耦合的架构,但实际上并非如此。
您真正要做的是将这种耦合从编译时转移到运行时,但是如果类 A 需要一些接口 B 才能工作,则仍然需要提供实现接口 B 的类的实例。
依赖注入应该只用于需要动态更改而不重新编译基本代码的应用程序部分。
我见过的对控制反转模式有用的用途:
想想你的设计。DI 允许您通过配置更改来更改代码的功能。它还允许您打破类之间的依赖关系,以便您可以更轻松地隔离和测试对象。你必须确定这在哪里有意义,在哪里没有意义。没有答案。
一个好的经验法则是,如果它太难测试,你就会遇到单一责任和静态依赖的问题。将执行单个功能的代码隔离到一个类中,并通过提取接口并使用 DI 框架在运行时注入正确的实例来打破该静态依赖关系。通过这样做,您可以轻松地分别测试这两个部分。
依赖注入应该只用于应用程序中需要动态更改而无需重新编译基础代码的部分
应该使用 DI 将您的代码与外部资源(数据库、Web 服务、xml 文件、插件架构)隔离开来。如果您正在测试依赖于数据库的组件,那么在许多公司中测试代码中的逻辑所花费的时间几乎是令人望而却步的。
在大多数应用程序中,数据库不会动态更改(尽管可以),但一般来说,不将应用程序绑定到特定的外部资源几乎总是一种好习惯。更改资源所涉及的数量应该很低(数据访问类的方法中的圈复杂度应该很少超过一个)。
“只是向类添加属性”是什么意思?
我的经验法则是使类单元可测试。如果您的类依赖于另一个类的实现细节,则需要将其重构/抽象到可以单独测试这些类的程度。
编辑:您在构造函数中提到了大量接口。我建议改用 setter/getter。我发现从长远来看,它使事情更容易维护。
只有当它有助于分离关注点时,我才会这样做。
就像跨项目一样,我会在我的一个库项目中为实现者提供一个接口,并且实现项目将注入他们想要的任何特定实现。
仅此而已……所有其他情况只会使系统变得不必要的复杂
即使拥有世界上所有的事实和过程......每个决定都归结为一个判断电话 - 我忘记了我在哪里读到的,
我认为这更像是一个经验/飞行时间电话。基本上,如果您将依赖项视为可能在不久的将来被替换的候选对象,请使用依赖注入。如果我将“classA 及其依赖项”视为一个替换块,那么我可能不会将 DI 用于 A 的部门。
最大的好处是它可以帮助你理解甚至揭示你的应用程序的架构。您将能够非常清楚地看到您的依赖链是如何工作的,并且能够对各个部分进行更改,而无需您更改不相关的内容。您最终会得到一个松散耦合的应用程序。这将推动您进行更好的设计,并且当您可以继续改进时您会感到惊讶,因为您的设计将帮助您继续分离和组织代码。它还可以促进单元测试,因为您现在可以自然地替换特定接口的实现。
有些应用程序只是一次性的,但如果有疑问,我会继续创建接口。经过一段时间的练习,负担并不大。
我纠结的另一个问题是我应该在哪里使用依赖注入?您对 StructureMap 的依赖在哪里?仅在启动应用程序中?这是否意味着所有的实现都必须从最顶层一直向下传递到最底层?
我使用 Castle Windsor/Microkernel,我没有其他任何经验,但我非常喜欢它。
至于你如何决定注射什么?到目前为止,以下经验法则对我很有帮助:如果类非常简单以至于不需要单元测试,您可以随意在类中实例化它,否则您可能希望通过构造函数获得依赖。
至于你是否应该创建一个接口而不是仅仅让你的方法和属性虚拟化,我认为你应该走接口路线,如果你要么a)可以看到类在不同的应用程序(即记录器)中具有一定程度的可重用性,要么b ) 如果由于构造函数参数的数量或构造函数中存在大量逻辑,则该类很难模拟。