使用依赖注入解耦
简介 在面向对象的设计中,有一个重要的原则 -- “解耦”。简单说(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,就抵消了在一开始使用接口的好处;并且 上述的例子应该如下:
在以上例子中,注意构造函数接受的是一个接口;不是接受一个具体的类。同时,注意如果orderDao参数为null的时候,抛出了异常。这样强调了获取依赖对象的合法性。以我的观点,构造函数注入,它最好的机制在于提供了对象必须的依赖。让开发这清楚地知道,调用Customer,在它能正常执行之前,需要提供哪些它所依赖的对象。然而,考虑以下状况……假设你的类有十个方法都不需要依赖,但你加一个方法需要依赖于IOrderDao。你的确可以改动构造函数,使用构造函数注入,但这会强迫你在所有地方改变原有的构造函数调用。当然,你也可以选择新加一个构造函数老获取依赖,但怎么样能让开发简单地知道使用哪个构造函数呢?最后,如果这个依赖的创建是很昂贵的,为什么要创建它并传入构造函数类,然而却很少使用它?设置方法注入(setter injection)可以在这种情况下使用。 设置方法注入不会强迫传递依赖对象到构造函数中。取而代之,是通过对象暴露出来的公有方法设置依赖。这样做法的动机主要包括以下几点: 1、在继承的类中,无需修改构造函数,就可实现依赖注入,而且; 以下代码使用设置方法注入替代刚才的构造函数注入:
在以上的例子中,构造函数没有参数。替代的是,在调用对象时,执行GetOrdersPlacedOn方法之前需要先设置IOrderDao依赖。构造函数注入而言,当依赖一开始没有注入时,程式立刻会抛出异常。例如,创建对象之前。对于设置方法注入而言,异常要到某个方法要使用依赖时才会抛出。还有注意的是,GetOrdersPlacedOn方法使用的是OrderDao属性,而不是直接使用私有的orderDao变量。这样,属性的getter方法才有机会验证依赖对象是否初始化。 使用设置方法注入替换构造函数注入时要谨慎,因为: 1、至少到“has not been initialized”抛出异常的时候,开发人员要清楚哪些依赖是需要的; 虽然如此,设置方法注入能提供新方法的同时尽量少改动原有的代码;如果所依赖的对象创建时非常昂贵或很困难的,此注入方法能提供性能上的加速。
单元测试的依赖注入:
使用DI控制器注入以来的一个主要好处就是它非常直接、容易地指出创建在何处发生。缺点就是仍然在某处存在依赖的硬编码;即使硬编码处于经常需要变动的位置。另一个缺点在于,现在DI控制器无法轻易地使用mock对象进行单元测试。(但我承认,某些强大的工具如TypeMock,可以在任何情况下生成注入mock对象。但是,类似TypeMock的工具应该只是用在绝对必要的情况下,因为他们引导你不使用“面对接口编程”的习惯。事实上,我推荐在非常困难得测试才使用。) 另一个实现注入的方式,就是使用应用程式容器…… 控制反转(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) 下面的代码展示了一个Mock的数据访问,实现了一个贯穿全文的IOrderDao接口。
一个简单的,Mock数据访问对象实现了IOrderDao,因此它能通过构造函数或设置方法注入的方法,传递到各个需要依赖IOrderDao 的对象。现在我们的逻辑层可以脱离访问数据库做测试。在我自己的测试包中,我通常有一段包含真正数据库的测试,然后传入Mock数据库对象以提供业务逻辑对象的单元测试。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |