默认构造函数与IOC容器
有人可以向我解释使用IOC容器的好处,而不是简单地将默认实现硬编码为默认构造函数吗?
换句话说,此代码有什么问题?
public class MyClass { private IMyInterface _myInterface; public MyClass() { _myInterface = new DefaultMyInterface(); } public MyClass(IMyInterface myInterface) { _myInterface = myInterface; } }
据我所知,该类足够支持构造函数注入,因此可以轻松进行单元测试和模拟。除此之外,默认构造函数还消除了IOC容器的计算开销(更不用说整个过程更加透明)。
我可以看到的使用IOC容器的唯一好处是,如果需要经常切换接口的实现。我想念什么吗?
解决方案
IoC的想法是将组件功能的一部分委托给系统的另一部分。在IoC世界中,我们拥有彼此不了解的组件。示例违反了这一点,因为我们正在MyClass和IMyInterface的某些实现之间创建紧密的耦合。主要思想是组件不知道如何使用它。在示例中,组件对其用法进行了一些假设。
实际上,这种方法是可行的,但是将IoC和显式对象初始化混合在一起并不是IMO的好习惯。
IoC通过执行后期绑定(以代码清晰为代价)使我们松散耦合。当我们在此过程中添加其他行为时,它会使事情变得更加复杂,并且当某些组件可能会接收到具有不良行为或者意外行为的对象时,可能会导致错误。
我不明白为什么硬编码默认实现的技术不能与IOC容器一起使用。只是,我们未在配置中指定的依赖项将采用默认实现。
还是我错过了什么?
我们可能想使用IOC容器的原因之一是可以方便我们对软件进行后期配置。
例如,假设我们提供了特定接口的几种实现,客户(或者专业服务团队)可以通过修改IOC配置文件来决定使用哪种接口。
选择一侧:)
简而言之,建议使用IOC。代码的问题在于,如果不像我们在最后所说的那样重新编译代码,则无法交换默认的依赖实现。 IOC允许我们更改外部文件中对象的配置或者组成,而无需重新编译。
IOC从其余的代码中接管"构造和装配"的职责。
IOC的目的不是使代码可测试……这是令人愉快的副作用。 (就像TDDed代码会导致更好的设计一样)
除了松耦合之外,IoC还可以减少代码重复。当我们使用IoC并想要更改接口的默认实现时,只需在一个位置进行更改。使用默认构造函数注入默认实现时,必须在使用该接口的所有位置进行更改。
这段代码没有任何问题,我们仍然可以将其与Dependency Injection框架(如Spring和Guice)一起使用。
许多开发人员将Spring的XML配置文件视为优于在代码中连接依赖项的一个优势,因为我们可以切换实现而无需编译步骤。实际上,在类路径中已经编译了多个实现并且想要在部署时选择实现的情况下,实际上可以实现这种好处。我们可以想象一种情况,其中组件在部署后由第三方提供。同样,在部署后,我们可能希望将其他实现作为补丁发布。
但是,并非所有的DI框架都使用XML配置。例如,Google Guice的模块编写为Java类,必须像其他任何Java类一样进行编译。
那么,如果我们甚至需要编译步骤,DI的优势是什么?
这将我们带回到原始问题。
我可以看到以下优点:
- 整个应用程序中DI的标准方法。
- 配置与其他逻辑完全分开。
- 能够注入代理。例如,Spring允许我们通过注入代理而不是实现来进行声明式事务处理
- 更容易重用配置逻辑。当我们广泛使用DI时,我们会看到复杂的依赖关系树随着时间的推移而演变。在没有明确分开的配置层和框架支持的情况下进行管理可能是一场噩梦。利用DI框架,可以通过继承和其他方式轻松地重用配置逻辑。
除了其他评论外,在这些情况下还可以提出DRY(请勿重复自己)原则。必须在每个类中放入默认的构造代码,这是多余的。它也引入了不需要任何特殊处理的特殊情况。
我唯一关心的是(1)如果默认服务依赖项本身具有另一个/辅助服务依赖项(依此类推...DefaultMyInterface取决于ILogger),并且(2)我们需要将第一个服务依赖项与第二个服务依赖项隔离开(需要使用存根ILogger测试DefaultMyInterface)。在那种情况下,我们显然需要丢失默认的" new DefaultMyInterface",而要执行以下操作之一:(1)纯依赖项注入或者(2)服务定位符或者(3)container.BuildUp(new DefaultMyInterface());
列出的其他海报中的一些问题可能对问题不公平。我们并不是在问多个"生产"实现。我们正在询问单元测试。就单元测试而言,我的第一条警告似乎是合情合理的。我也考虑在简单的单元测试用例中使用它。
同样,一些回应者对可耻性表示关注。我也不喜欢重新填充,但是如果(1)默认实现确实是默认(YAGNI:我们没有更改默认值的计划),以及(2)我们不相信我所说的第一个警告适用,并且(3)喜欢我们共享的更简单的代码方法,那么我认为这种特殊形式的重新布局不是问题。