依赖注入(或控制反转).我觉得大多数关于依赖注入的文章都过于拘泥于用于演示该结构的任何示例的细节。在本文中,我们将介绍纯抽象。
也许不是纯抽象——毕竟我们必须选择一种特定的编程语言!在本文中我们将使用Java。如果你不懂Java,也不用太担心。我们将坚持使用“基本”Java—没有什么深奥的东西。
典型的依赖情况
考虑以下依赖项情况,其中类Cls
这两者都取决于接口Intf
在一个实现中Impl
这个界面。
公共接口Intf{无效helper方法(对象参数);}公共类Impl extends Intf{@覆盖公共无效helperMethod(对象参数){/* implementation */}}公共类Cls{公共无效方法(对象参数){Intf Intf = new Impl();intf.helperMethod (args);}}
Cls
取决于在Impl
因为这需要知识Impl
键入以便执行新的Impl ()
.重申一下,我们目前的依赖情况是:
Cls
它- - - - - - >Impl
Cls
————>Intf
Impl
————>Intf
我们想要处于一种依赖状态Cls
只取决于Intf
而不是在Impl
.也就是说,我们想解耦实施Cls
的任何特定实现Intf
.正如一些书中所描述的微软文档,此解耦是需要的,因为:
Intf
由Cls
的体中不修改代码*Cls
.有一种简单的方法可以将一个实现交换为另一个实现,从而可以轻松地交换实现,这有助于测试驱动的开发。
它消除了手动配置的需要
Impl
*如果你使用的是Spring Framework for Java,你可以通过改变的注释来改变注入的实现Cls
.的主体中,我不认为这是对“实际代码”的更改Cls
!诚然,它将如果改变接口实现没有触及,那就更好了任何东西内部Cls
,这样配置代码(指定注入哪个接口的代码)就完全独立于实现代码。c# . net执行依赖注入的方式更好地遵循了这条规则。
更好的依赖情况
为了改善我们的依赖状况,我们会传递创造的责任Impl
一些容器
类,用于管理Cls
.接到命令后,容器
会“注射”吗新
ly创建Impl
实例进Cls
.执行这种“注射”的一种方法是,构造函数注入,就是传递一个Impl
的构造函数Cls
它接受Impl
.(当然,Cls
必须有一个公共的带参数的构造函数,构造函数注入是可能的)。还有其他形式的依赖项注入,例如setter注入而且字段注入.(在Java中,字段注入是基于滥用反射技术修改私人
字段)。
这个设置叫做依赖注入。我实现依赖注入将我们置于以下大大改善的依赖情况:
Cls
————>Intf
容器
————>Cls
容器
————>Intf
容器
它- - - - - - >Impl
Impl
————>Intf
现在,Cls
只取决于Intf
而不是在Impl
,如你所愿。
除了解决解耦问题之外,依赖注入还带来了一个巨大的好处,即允许将单个接口实现“注入”到依赖于同一接口类型的不同类中,如果这样做是有意义的。换句话说,我们新的依赖情况也允许共享一个Intf
类实例之间的实例。
总结
总结一下,这里有依赖注入的两个主要好处:
依赖注入将类的实现与这些类的依赖的实现解耦。接口与实现的解耦是可取的,因为……
1.1.它允许我们在不修改类主体代码的情况下更改依赖类使用的接口实现。
实现,这有助于测试驱动的开发。
1.2.它消除了手动配置注入实现的依赖项的需要。
依赖注入允许我们在依赖上述接口的多个类之间共享一个接口实现实例。
控制反转
因为,在依赖注入中,容器
,而不是Cls
,控制内容和时间Cls
的依赖被注入,控制在某种意义上被反转。依赖项注入就是一个例子控制反转。出于这个原因,类如容器
通常被称为控制容器的反转,或IoC容器.
注意,虽然依赖注入是控制反转的一个例子,但并非所有的控制反转都是依赖注入。这篇文章Martin Fowler详细介绍了其他控制反转的例子。
代码
下面是实现依赖注入模式的代码。(以下基本上是对中给出的代码的抽象外推 依赖注入)。
公共接口Intf{无效helper方法(对象参数);}公共类Impl扩展Intf{私有对象参数;public Impl(对象参数){this。Args = Args;} @覆盖公共无效helperMethod(){/*实现使用此方法。args */}}公共类Cls{私有intf;public Cls(Intf Intf) {this.intf = Intf;}公共无效方法(){intf.helperMethod();一个配置文件通常会执行这个方法的任务。*/ private Container configureContainer(){对象参数=…//获取应该传递给Impl构造函数的参数Container cntr = new Container();/*下面一行告诉cntr执行语句"Intf Intf = Impl(args)"所需的所有信息。 */ cntr.registerComponentImplementation(Intf.class, Impl.class, args); /* This next line tells cntr all the information it needs to execute the statement "Cls cls = new Cls(intf)". */ cntr.registerComponentImplementation(Cls.class); return cntr; } public static void main(String[] _args) { /* This is how we call cls.method(). */ Container cntr = configureContainer(); Cls cls = (Cls) cntr.getComponentInstance(Cls.class); cls.method(); // This executes the same task as "cls.method(args)" did in the original situation. } }
附言:我们付出了吗容器
不必要的信息?
其中一个是关于线条的cntr.registerComponentImplementation ()
我不是很清楚,你可能也很困惑。我的问题是:有必要通过吗Intf.class
的第一声呼唤registerComponentImplementation ()
?的实现似乎应该存在容器
如果我们执行以下命令,容器
就像我们期待的那样。
cntr.registerComponentImplementation (Impl.class, args);cntr.registerComponentImplementation (Cls.class);
也就是说,似乎容器
有足够的信息做什么(new Impl(args))
.这是因为容器
有一个Cls
,Cls
知道Impl
是一个Intf
.为了便于讨论,即使我们假设容器
在某种程度上没有了解这个Cls
JVM本身知道这一点Impl
是一个Intf
——毕竟,执行(new Impl(args))
不需要我们输入类型转换新Impl (args)
来Intf
!
答:经过调查,我发现在某些依赖注入框架中传递比严格需要的更多的信息只是一种惯例。