体系结构

对依赖注入模式的抽象理解

本文将相对抽象地介绍一种名为依赖注入(或控制反转.我觉得大多数关于依赖注入的文章都过于拘泥于用于演示该结构的任何示例的细节。在本文中,我们将介绍纯抽象。

也许不是抽象——毕竟我们必须选择一种特定的编程语言!在本文中我们将使用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.正如一些书中所描述的微软文档,此解耦是需要的,因为:

  1. 它允许我们更改哪个实现IntfCls的体中不修改代码*Cls

    • 有一种简单的方法可以将一个实现交换为另一个实现,从而可以轻松地交换嘲笑实现,这有助于测试驱动的开发。

  2. 它消除了手动配置的需要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.它允许我们在不修改类主体代码的情况下更改依赖类使用的接口实现。

    • 有一种简单的方法可以将一个实现交换为另一个实现,从而可以轻松地交换嘲笑实现,这有助于测试驱动的开发。

    1.2.它消除了手动配置注入实现的依赖项的需要。

  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.为了便于讨论,即使我们假设容器在某种程度上没有了解这个ClsJVM本身知道这一点Impl是一个Intf——毕竟,执行(new Impl(args))不需要我们输入类型转换新Impl (args)Intf

答:经过调查,我发现在某些依赖注入框架中传递比严格需要的更多的信息只是一种惯例。

留下回复

这个网站使用Akismet来减少垃圾邮件。了解如何处理您的评论数据

罗斯Grogan-Kaylor

Ross Grogan-Kaylor是Perficient明尼阿波利斯办公室的副技术顾问。他喜欢研究语法中的结构模式和软件开发的高级思想。

更多来自作者

订阅每周博客文摘:

报名
关注我们
推特 Linkedin 脸谱网 Youtube Instagram