- 代码量更大
- 面向瑞士卷式设计而非面条式设计
- 性能较慢,需要在构造函数中初始化所有依赖项,即使我想要调用的方法只有一个依赖项
- 在没有使用IDE的情况下难以理解
- 有些错误被推迟到运行时才会出现
- 添加额外的依赖项(DI框架本身)
- 新员工必须首先学习DI才能开始工作
- 有很多样板代码,这对于有创造力的人来说是不好的(例如将构造函数中的实例复制到属性中...)
你所关心的大部分问题似乎归结为误用或误解。
更大的代码体积
这通常是恰当尊重单一职责原则和接口隔离原则的结果。它是否显著增加呢?我怀疑不像你声称的那么大。然而,它最有可能实现的是将类分解为特定的功能,而不是拥有“万能”类来完成任何和所有任务。在大多数情况下,这是健康的关注点分离的迹象,而不是问题。
像意大利千层面般复杂的代码
再次,这很可能会让您考虑堆栈而不是难以查看的依赖性。 我认为这是一个巨大的好处,因为它导致适当的抽象和封装。
性能变慢 只需使用快速容器即可。 我最喜欢的是SimpleInjector和LightInject。
需要在构造函数中初始化所有依赖项,即使我要调用的方法只有一个依赖项
同样,这表明您正在违反单一职责原则。这是一件好事,因为它迫使您在逻辑上考虑您的架构,而不是随意添加。
没有使用IDE时更难理解,有些错误会推到运行时
如果您仍然没有使用IDE,那么真丢人。对于现代计算机来说,没有好的理由。此外,一些容器(SimpleInjector)将在首次运行时进行验证(如果您选择的话)。您可以通过简单的单元测试轻松检测到这一点。
添加额外的依赖项(DI框架本身)
你必须斟酌利弊。如果学习一个新框架的成本低于维护意大利面条般复杂的代码的成本(我怀疑它会),那么成本是合理的。
新员工必须先学习DI才能使用它
如果我们回避新的模式,我们永远不会成长。我认为这是丰富和发展您的团队的机会,而不是伤害他们的方式。此外,权衡是学习可能比掌握行业通用模式更困难的混乱代码。
很多样板代码对于有创意的人来说是不好的(例如从构造函数复制实例到属性...)
这是完全错误的。强制依赖项应始终通过构造函数传递。只有可选依赖项应该通过属性设置,并且在非常特定的情况下才应该这样做,因为这经常违反单一责任原则。
我们不测试整个代码库,但只测试某些方法并使用真实数据库。那么,当测试不需要模拟时,是否应避免使用依赖注入?
我认为这可能是最大的误解。依赖注入不仅仅是为了使测试变得更容易。它是为了让您可以查看类构造函数的签名并立即知道使该类运转所需的内容。这在静态类中是不可能的,因为类可以随心所欲地调用上下堆栈而没有条理或理由。您的目标应该是为您的代码添加一致性、清晰度和区分度。这是使用DI的最大原因,也是我强烈建议您重新审视它的原因。
这意味着您正在违反重用抽象原则。通常,应用程序中的大多数组件/类都应该由十几个抽象覆盖。例如,实现某些用例的所有类可能值得一个单一(通用)的抽象。实现查询的类也值得一个抽象。对于我编写的系统,80%至95%的组件(包含应用程序行为的类)都由5到12个(主要是通用的)抽象覆盖。大多数情况下,您不需要仅为接口创建新项目。大多数情况下,我将这些接口放在同一项目的根目录中。增加更多接口等项目
你所编写的代码量最初可能并没有太大的不同。然而,依赖注入的实践只有在应用SOLID时才能发挥出它的优势,而SOLID则倡导小型专注类。只有一个单一职责的类。这意味着你将拥有许多易于理解和易于组合成灵活系统的小型类。别忘了:我们不应该追求写更少的代码,而是更易于维护的代码。更大的代码量
这有点真实。依赖注入促进类之间解耦,但有时会使浏览代码库变得更加困难,因为一个类通常依赖于抽象而不是具体的类。过去我发现DI给我的灵活性远远超过了查找实现的成本。使用Visual Studio 2015,我可以简单地按CTRL + F12查找接口的实现。如果只有一个实现,Visual Studio将直接跳转到该实现。相较于意大利瑞士卷饼式代码 当没有使用IDE时更难理解
有些错误是在运行时被推送出来的 添加额外的依赖项(DI框架本身)
这些问题都意味着需要使用DI库。DI库会在运行时进行对象组合。然而,使用依赖注入时,并不一定需要DI库作为必备工具。小型应用程序可以从使用不带工具的依赖注入中受益;这被称为Pure DI。您的应用程序可能不会从使用DI容器中受益,但大多数应用程序实际上会从依赖注入(正确使用时)中受益作为一种实践。再次强调:工具是可选的,编写可维护的代码则是必须的。
但即使您使用DI库,也有一些内置工具的库可让您验证和诊断配置。它们不会给您提供编译时支持,但它们允许您在应用程序启动时或使用单元测试运行此分析。这样可以避免您对整个应用程序进行回归测试,仅仅是为了验证您的容器是否正确连接。我的建议是选择一个DI容器,以帮助您检测这些配置错误。
这有点道理,但是依赖注入本身并不难学。真正难学的是正确地应用SOLID原则,而如果您想编写需要在考虑到一段时间内由多个开发人员维护的应用程序,那么您无论如何都需要学习这些内容。我更愿意投资于教导团队中的开发人员编写符合SOLID原则的代码,而不仅仅是让他们编写代码;否则,这肯定会在以后造成维护上的麻烦。新员工必须首先学习DI才能开始使用它
警告:我讨厌IoC。
这里有很多令人放心的伟大答案。根据Steven(非常强的答案)的说法,主要优点包括:
- 提高可测试性
- 提高灵活性
- 提高可维护性
- 提高可扩展性
然而我的经验与此截然不同,以下是一些平衡的观点:
(奖励) 愚蠢的Repository模式
经常会将其与IoC一起使用。 Repository模式应仅用于访问外部数据,并且可互换性是核心期望之处。
当您使用Entity Framework时,如果使用了Repository模式,则禁用了Entity Framework的所有功能,Service Layers也是如此。
例如,调用:
var employees = peopleService.GetPeople(false, false, true, true); //Terrible
应该是:
var employees = db.People.ActiveOnly().ToViewModel();
你没有选择使用IOC库(StructureMap、Ninject、Autofac等)的原因是什么?使用它们中的任何一个都会使你的生活变得更加轻松。
虽然David L已经对你的观点做出了优秀的评论,但我也会补充自己的观点。
我不确定你是如何得到更大的代码库的;IOC库的典型设置非常小,而且由于你在类构造函数中定义了你的不变量(依赖关系),所以你也删除了一些不再需要的代码(即“new xyz()”之类的东西)。
我碰巧很喜欢意大利肉酱面:)
public class Something
{
readonly IFrobber _frobber;
public Something(IFrobber frobber)
{
_frobber=frobber;
}
public void LetsFrobSomething(Thing theThing)
{
_frobber.Frob(theThing)
}
}
IFrobber
实现;它只代表抽象能力来Frob某些东西,你不需要在脑海中记住任何特定的Frobber可能如何工作。你可以集中精力确保这个类做了它应该做的事情 - 即委托一些工作给某种类型的Frobber。这与任何新技术的情况没有什么不同 :) 学习使用IOC库往往会开启头脑,让人想到其他可能性,如TDD、SOLID原则等等,这从来不是一件坏事!
我不理解这个问题,你可能会产生大量样板代码;我不认为将给定的依赖项存储在私有只读成员中算是值得谈论的样板代码 - 请注意,如果每个类有超过3或4个依赖项,则可能违反SRP并应重新考虑设计。
最后,如果您还不相信这里提出的任何论点,我仍然建议您阅读Mark Seeman的“ .Net中的依赖注入”。(或者他在博客上发表的任何其他关于DI的观点)我保证你会学到一些有用的东西,而且我可以告诉你,它改变了我编写软件的方式。
如果你必须在代码中手动初始化依赖项,那么你做错了什么。IoC的一般模式是构造函数注入或者可能是属性注入。类或控制器根本不应该知道DI容器。
通常,你所需要做的就是:
Interface = Class in Singleton scope
Controller(Interface interface) {}
我没有看到任何样板代码、性能下降或其他你所描述的问题。我真的无法想象如何编写更复杂或更简单的应用程序而没有它。
但总的来说,你需要决定什么更重要。是取悦“有创意的人”还是建立可维护和强大的应用程序。
顺便说一句,要从构造函数创建属性或字段,你可以在R#中使用Alt+Enter
,它会为你完成所有工作。