我将以数字方式详细说明您的观点,但首先,您需要特别注意以下几点:不要将消费者如何使用库与实现库的方法混为一谈。很好的例子就是实体框架(您自己会引用它作为一个好的库)和ASP.NET的MVC。两者都在幕后做了大量工作,例如反射,如果您通过日常代码进行传播,则绝对不会认为这是好的设计。
这些库在工作方式或幕后工作方面绝对不是 “透明的”。但这不是有害的,因为它们在其消费者中支持良好的编程原则。因此,每当谈到这样的库时,请记住,作为库的使用者,您不必担心其实现或维护。您只需要担心它如何帮助或阻碍您编写的使用该库的代码。不要混淆这些概念!
因此,要逐点进行:
立即我们可以得出我只能假设的上述例子。您说IOC容器将大多数依赖项设置为静态。好吧,也许它们如何工作的一些实现细节包括静态存储(尽管考虑到它们倾向于将像Ninject的对象的实例IKernel
作为其核心存储,我什至对此表示怀疑)。但这不是您关心的问题!
实际上,一个IoC容器在范围上与穷人的依赖注入一样明确。(我将继续将IoC容器与穷人的DI进行比较,因为将它们与根本没有DI的容器进行比较将是不公平和令人困惑的。而且,很明显,我没有使用“穷人的DI”作为贬义词。)
在穷人的DI中,您将手动实例化一个依赖项,然后将其注入需要它的类中。在构造依赖项时,您将选择如何处理它-将其存储在局部作用域变量,类变量,静态变量中,无论如何都不要存储。您可以将同一实例传递给许多类,也可以为每个类创建一个新实例。随你。关键是要查看发生了什么,您可以查看可能在应用程序根目录附近的位置,在该位置创建依赖项。
现在,一个IoC容器呢?好吧,你做的完全一样!此外,通过Ninject术语去,你看看绑定设置,以及知道它是否说像InTransientScope
,InSingletonScope
或什么的。如果有什么可能更清楚的话,因为您在那里有代码声明了它的作用域,而不必查看方法来跟踪某个对象发生了什么(一个对象可能作用域为一个块,但是它在其中多次使用了例如,阻止或仅阻止一次)。因此,也许您对必须使用IoC容器上的功能而不是原始语言功能来指示作用域的想法感到反感,但是只要您信任IoC库(您应该这样做),就没有真正的缺点。 。
我仍然真的不知道你在说什么。IoC容器是否将私有属性视为其内部实现的一部分?我不知道他们为什么会这样做,但是再次重申,如果这样做的话,您所关心的是如何实现所使用的库并不是您的问题。
或者,也许,他们具有像注入私人二传手那样的能力?老实说,我从来没有遇到过这个问题,并且我怀疑这是否真的是一个常见功能。但是,即使它存在,这也是一种容易被滥用的工具的简单案例。请记住,即使没有IoC容器,也只需几行反射代码即可访问和修改私有属性。这是您几乎永远都不应做的事情,但这并不意味着.NET不利于公开此功能。如果有人如此明显地并且疯狂地滥用了工具,那是该人的过错,而不是工具的过错。
这里的最终点类似于2。在大多数情况下,IoC容器都使用构造函数!在非常特殊的情况下(由于特殊原因而不能使用构造函数注入)提供了Setter注入。一直使用setter注入来掩盖传入的依赖项的任何人都在大规模地使用该工具。那不是工具的错,是他们的错。
现在,如果这是一个很容易犯的无辜错误,并且IoC容器鼓励这样做,那么好吧,也许您有意思。这就像让班上的每个成员都公开,然后在别人修改自己不应该做的事情时责备别人,对吗?但是,任何使用setter注入掩盖违反SRP的人要么故意忽略要么完全不了解基本设计原则。将此归咎于IoC容器是不合理的。
这是特别正确的,因为您也可以使用穷人的DI轻松地做到这一点:
var myObject
= new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
因此,实际上,这种担忧似乎与是否使用IoC容器完全正交。
更改类的协作方式并不违反OCP。如果是这样,那么所有依赖倒置都会鼓励违反OCP。如果是这种情况,它们将不会使用相同的SOLID首字母缩写!
而且,a)和b)都与OCP无关。我什至不知道如何针对OCP回答这些问题。
我唯一能猜到的是,您认为OCP与在运行时未更改行为有关,或者与在代码中控制依赖项生命周期的位置有关。不是。当添加或更改需求时,OCP不必修改现有代码。这全都与编写代码有关,而不是与您已经编写的代码如何粘合在一起(当然,松耦合是实现OCP的重要组成部分)。
最后一句话,您说:
但是我不能再信任第三方工具来改变我的已编译代码,以执行我无法手动执行的操作的变通办法。
是的,您可以。您绝对没有理由认为,比起其他任何第三方库,大量项目所依赖的这些工具更容易出错或发生意外行为。
附录
我只是注意到您的介绍段落也可以使用一些寻址方式。您讽刺地说,IoC容器“没有采用我们从未听说过的某些秘密技术”,以避免使用混乱且易于复制的代码来构建依赖关系图。而且您说的很对,他们实际上正在使用与程序员一样的基本技术来解决这些问题。
让我谈谈一个假设的情况。作为程序员,您将一个大型应用程序组合在一起,并且在构建对象图的入口处,您会发现代码很杂乱。有很多类被一次又一次地使用,并且每次您构建其中一个类时,都必须在其下再次构建整个依赖链。另外,您发现除声明代码或控制依赖项的生命周期外,没有任何表达方式,除非每个方法都有自定义代码。您的代码是非结构化的,并且充满重复性。这是您在介绍段落中谈到的混乱情况。
因此,首先,您开始重构位,其中一些重复的代码已足够结构化,可以将其拉入辅助方法,依此类推。但是随后您开始思考-这是我可能可以从一般意义上解决的一个问题,这个问题不是特定于该特定项目的,但可以在您将来的所有项目中为您提供帮助吗?
因此,您坐下来考虑一下,然后决定应该有一个可以解决依赖关系的类。然后,您将勾画出所需的公共方法:
void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();
Bind
说“在您看到类型的构造函数参数时interfaceType
,传入的实例concreteType
”。附加singleton
参数说明是concreteType
每次使用相同的实例,还是始终创建一个新实例。
Resolve
将简单地尝试T
使用任何可以发现其参数均为先前绑定类型的构造函数进行构造。它还可以递归调用自身,以完全解决依赖关系。如果由于未绑定所有内容而无法解析实例,则会引发异常。
您可以尝试自己实现这一点,您会发现需要一些反思,并且需要singleton
为正确的绑定进行一些缓存,但是肯定不会有任何激烈或恐怖的事情。一旦完成,瞧,您就拥有了自己的IoC容器的核心!真的那么恐怖吗?这与Ninject或StructureMap或Castle Windsor或您所喜欢的任何东西之间的唯一真正区别是,它们具有很多功能,可以满足基本版本不足的(很多!)用例。但从本质上讲,您拥有的是IoC容器的本质。
IOC
和之间的+1相关Explicitness of code
正是我遇到的问题。手动DI可以很容易地成为一件繁琐的事情,但是至少将独立的显式程序流程放在一个地方-“您得到所见即所得”。虽然IOC容器的绑定和声明可以轻松地自己成为隐藏的并行程序。