使用依赖注入解耦

前端之家收集整理的这篇文章主要介绍了使用依赖注入解耦前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

简介

在面向对象的设计中,有一个重要的原则 -- “解耦”。简单说(loosely),不是一语双关(这里使用loosely和loose coupling没有任何关系,只是语法相似),“解耦”的意思就是说,一个对象工作时需要依赖一些对象,而这些依赖应该越少越好。此外,当可能的时候,对象依赖的应该是接口,而不是具体具体化的类。(具体得累就是用关键字“new”常见的实例。)“解耦”能促进更好的重用、增加可维护性,并且允许你能很容易地提供“模仿”(Mock)对象替代昂贵的服务,例如端口交流器(socket-communicator)。

“依赖注入”("Dependency Injection",简称DI),和更神秘的概念“控制反转”(Inversion of Control" ,简称IoC),是一项提供解耦方式的技术。主要有两个实现DI的的方式:构造函数注入(constructor injection)和设置方法注入(setter injection)。显然,在某些点上,必须要“某个东西”有创建具体的类的职责,以提供对象,可供注入到另一个对象。这个注入者(injector)可以为一个父类,我们称为“依赖注入控制端”(DI controller);或者能被一个“依赖注入容器”(DI container)在外部处理实现。以上就是几个主要的使用依赖注入的概观。

构造函数注入(Constructor Injection)

使用构造函数参数传递对象依赖的DI(依赖注入以下都简称为DI)技术,就是构造函数注入。以下的这个例子,包括一个类,Customer,它暴露了一个方法去获得每份销售订单所属客户的详细日期信息。因此,Customer类需要一个数据访问类与数据库关联。假设,现在有一个类orderDao (全名"order data-access object"),实现接口IOrderDao.有一种方法,Customer类可以执行以下代码获得依赖:

这样做,有2个主要的缺点。

1、在本地实例化OrderDao,就抵消了在一开始使用接口的好处;并且
2、OrderDao不能轻易地被用来测试的Mock对象替换。(Mock对象会稍后讨论)

上述的例子应该如下:

在以上例子中,注意构造函数接受的是一个接口;不是接受一个具体的类。同时,注意如果orderDao参数为null的时候,抛出了异常。这样强调了获取依赖对象的合法性。以我的观点,构造函数注入,它最好的机制在于提供了对象必须的依赖。让开发这清楚地知道,调用Customer,在它能正常执行之前,需要提供哪些它所依赖的对象。然而,考虑以下状况……假设你的类有十个方法都不需要依赖,但你加一个方法需要依赖于IOrderDao。你的确可以改动构造函数,使用构造函数注入,但这会强迫你在所有地方改变原有的构造函数调用。当然,你也可以选择新加一个构造函数获取依赖,但怎么样能让开发简单地知道使用哪个构造函数呢?最后,如果这个依赖的创建是很昂贵的,为什么要创建它并传入构造函数类,然而却很少使用它?设置方法注入(setter injection)可以在这种情况下使用。

设置方法注入(Setter Injection)

设置方法注入不会强迫传递依赖对象到构造函数中。取而代之,是通过对象暴露出来的公有方法设置依赖。这样做法的动机主要包括以下几点:

1、在继承的类中,无需修改构造函数,就可实现依赖注入,而且;
2、允许尽可能晚的,而且在需要的情况下,才创建某些昂贵的资源或服务。

以下代码使用设置方法注入替代刚才的构造函数注入:

在以上的例子中,构造函数没有参数。替代的是,在调用对象时,执行GetOrdersPlacedOn方法之前需要先设置IOrderDao依赖。构造函数注入而言,当依赖一开始没有注入时,程式立刻会抛出异常。例如,创建对象之前。对于设置方法注入而言,异常要到某个方法要使用依赖时才会抛出。还有注意的是,GetOrdersPlacedOn方法使用的是OrderDao属性,而不是直接使用私有的orderDao变量。这样,属性的getter方法才有机会验证依赖对象是否初始化。

使用设置方法注入替换构造函数注入时要谨慎,因为:

1、至少到“has not been initialized”抛出异常的时候,开发人员要清楚哪些依赖是需要的;
2、加大了一点难度去跟踪异常的来源和原因。

虽然如此,设置方法注入能提供新方法的同时尽量少改动原有的代码;如果所依赖的对象创建时非常昂贵或很困难的,此注入方法能提供性能上的加速。

注入器(The Injectors)

紧接着,下一个问题就是,什么东西能创建出依赖对象,注入到“被注入者”?有2个试点那个的地方以供创建这些逻辑:控制器和容器。

DI 控制器(DI Controllers)

DI控制器的创建步骤是容易理解和实现的。在一个适当分层的架构中,一个应用程式拥有不同的层处理逻辑。最简单的分层通常包括数据库通讯的数据层,负责显示UI的表现层,履行业务逻辑的区域逻辑层。即使没有很好地定义,“控制器”层总是存在,为了定位UI事件到区域和数据层,反之亦然。举个例子,在ASP.NET中,code-behind页面扮演基础控制层。还有更多形式化的控制层存在:Java中的Struts和Spring;.NET的Front Controller 和 Spring .NET 。全部这些实现都遵守着MVC(Model-View-Controller )模式形态。不管你用什么作为你的控制器,这个控制器就是实现依赖注入配件的适合所在;这里是实例化、注入依赖对象的地方。接着两个例子演示使用控制器实现DI。第一个例子是一个示例性的例子 “生成注入代码”-- 你要自我完成配置。第二个是“测试代码”示例 -- 用来测试应用程式的,但没有完整,并不一定需要一个真实的数据库

控制器代码实现依赖注入(如,ASP.NET code-behind页面):

单元测试的依赖注入:

使用DI控制器注入以来的一个主要好处就是它非常直接、容易地指出创建在何处发生。缺点就是仍然在某处存在依赖的硬编码;即使硬编码处于经常需要变动的位置。另一个缺点在于,现在DI控制器无法轻易地使用mock对象进行单元测试。(但我承认,某些强大的工具如TypeMock,可以在任何情况下生成注入mock对象。但是,类似TypeMock的工具应该只是用在绝对必要的情况下,因为他们引导你不使用“面对接口编程”的习惯。事实上,我推荐在非常困难得测试才使用。)

在ASP.NET,我更喜欢使用Model-View-Presenter (MVP)模式,ASP.NET code-behind 页面创建依赖,通过构造函数注入到presenter。另外,我使用用户控件作为模式的View部分,因此ASP.NET code-behind在用户控件(View视图)和他们的Presenter之间,纯粹扮演着MVP“依赖初始化”的角色。

另一个实现注入的方式,就是使用应用程式容器……

DI容器(DI Containers)

控制反转(Inversion-of-Control)/依赖注入(Dependency-Injection)容器,无论哪一部分细节的事件触发,都可以监测应用程式、注入依赖。举例,当Customer实例创建时,他可以自动被注入所需要的依赖。首先,这个是非常奇怪的一个概念,但这对于管理大型应用程式多个服务依赖非常有效。不同的容器拥有它们各自的机制,提供管理依赖注入的设置。

Spring .NET允许你使用XML文件定义依赖注入。以下例子,Spring.NET XML使用设置方法注入,为ASPX code-behind page提供数据访问对象的依赖:

ASPX code-behind 简单地公开一个名为DaoFactory的属性,当页面被呼叫时,来获取所需要的依赖。到Spring .NET's website 可以获取更详细信息和示例。对于Java开发者,肯定参观过Spring's website。

有许多其他的容器,有些根本并不需要很多的XML管理(你有时会看到令你畏惧的500行壮观XML文件)。对于Java开发者,看看Container Comparison,有很好的对比观点。对于.net开发者,现在的选择比较少(可能是个好事?)。看看一些开源的建议Open Source Inversion of Control Containers in C#

使用注入Mock对象做单元测试(Unit Testing with Injected Mock Objects)

据经验所得,使用DI最大的好处就是更干净的单元测试和非常好的可插拔性。由于接口,依赖带来了可插拔性。我们来看看DI怎么有利于单元测试。这是一个经常的用例,开发者应对真实数据库编写单元测试。避免让单元测试成为累赘,这需要摒弃以前的慢速单元测试 -- 测试业务逻辑层的时候所依赖的数据访问代码,要不断地访问数据库。Mock对象是实现这个的完美选择。Mock对象模仿真实对象的反应,扮演假设使用真正资源的角色。他们可以很好地模仿访问数据库,模仿访问IO,模仿访问Web Service等等。

下面的代码展示了一个Mock的数据访问,实现了一个贯穿全文的IOrderDao接口。

一个简单的,Mock数据访问对象实现了IOrderDao,因此它能通过构造函数或设置方法注入的方法,传递到各个需要依赖IOrderDao 的对象。现在我们的逻辑层可以脱离访问数据库做测试。在我自己的测试包中,我通常有一段包含真正数据库的测试,然后传入Mock数据库对象以提供业务逻辑对象的单元测试。

引用文章

  • Inversion of Control Containers and the Dependency Injection Pattern
  • Unit testing with mock objects
  • 原文链接:https://www.f2er.com/javaschema/287570.html

    猜你在找的设计模式相关文章