接口驱动设计
接口驱动设计几乎已经成为了一种实现应用程序的习惯,无论是框架还是你使用的一些标准(Spring,jboss,guice,EJB等等)。这样设计的一个主要目标是进一步的简化应用程序开发。在详细讨论设计细节之前,有必要阐述下为什么这样设计如此重要,以及spring是如何简化它的。
为什么要接口驱动设计
设计成接口驱动而不是直接使用类有很多原因,但也许最重要的原因就是降低耦合。如果一个容器使用接口来编写,当它有问题时将很容易被替换。这项特性将使你能够方便的将一种实现转为另一种,而不需要影响其他的代码。事实上,这么做你甚至可以使你的程序可以使用多种不同的实现并存。比如,当你使用spring 的prifile特性时,你能够在不影响其他代码的情况下提供给一个接口以不同的实现。不仅如此,在java里,一个类只能继承一个父类却能够实现多个不同的接口。通过定义接口,你不用使用一个类去继承特定的类(非常大的抽象类),而是实现多个接口。另一个好处,如我们在第七章讨论的,当你使用面向切面思想时,使用接口驱动让你能够使用spring的introduction特性,从而在你的类中 “嵌入”特定的接口,同时又不影响其他代码和逻辑。
在测试方面,一个主要的好处是通过降低耦合使得测试更容易。作为开发者的一员,我们一直在寻求新的方式来改进我们的程序测试覆盖率。通过接口驱动设计,我们可以方便的使用模拟来替换那些接口实现,从而使我们的测试更灵活。比如,当我们做单元测试一个web controller的时候,我们只想关注是否这个controller工作正常,并且假定那些service都是没问题的。在这种情况下,使用模拟service将会非常的合适。
工厂模式
在实现程序时可能会碰到一个非常关键的问题:将所有的容器都定义为接口后,程序将如何确定它使用哪个实例?
一个传统的方法是使用工厂模式。工厂模式定义了那些用来实现接口的实现类。在这种情况下,容器是以接口定义而不是类定义的。假设有个业务接口叫OrderService。其他容器需要获得这个实例,但又不能确定何时要,该如何实现? 通常做法是创建一个工厂类如12-1代码。
//Listing 12-1. A Basic Factory Class Implementation
package com.apress.prospring3.ch12.factory;
import com.apress.prospring3.ch12.service.OrderService;
import com.apress.prospring3.ch12.service.impl.DefaultOrderServiceImpl;
public class BasicFactory {
private static final BasicFactory instance;
private OrderService orderService;
static {
instance = new BasicFactory();
}
public static BasicFactory getlnstance() {
return instance;
}
public BasicFactory() {
this.orderService = new DefaultOrderServiceImpl();
}
public OrderService getOrderService() {
return this.orderService;
}
}
这是个非常简单的工厂实现例子。现在程序要得到OrderService的实现只要使用BasicFactory的getOrderService()方法就可以了。
传统工厂模式的缺点
传统的工厂模式主要有如下三个缺点:
-除非重新编译,否则没法修改实现类
-容器没有办法简单的使用需要的实现类。容器必须对工厂类有所了解从而知道该调用工厂类的哪个方法来获得实现。
-无法简单的修改实现类。在12-1例子中,返回了一个的实例,但是假设我们想同时需要多个实例,我们不得不修改并重新编译工厂类,我们将在接下来的三个小节中对这些缺点的详细讨论
可外部配置的工厂类
在12-1的例子中,你可以看到修改实现类意味着修改并重新编译。接口驱动设计的一个好处就是可以方便的替换实现类。然而,重新编译工厂类使其大打折扣。在过去的许多工程中,那时还没有Spring, 我们通常创建一个可以在外部配置文件中配置的工厂类来避免这个问题。这的确解决了开头的问题,但是它也使得我们不得不承担更多的开发工作,更糟的是,即使如此还是有问题。。。
显示的多实现支持
显示的多实现支持也许是传统工厂模式最大的缺点。在类BasicFactory中,getOrderService()方法,只能返回唯一一个特定的实现,无法自由选择。于是很自然的我们想到了如12-2的实现方式:
//Listing 12-2. Basic Support for Multiple Implementations
package com.apress.prospring3.ch12.factory;
import com.apress.prospring3.ch12.service.OrderService;
import com.apress.prospring3.ch12.service.impl.DefaultOrderServiceImpl;
import com.apress.prospring3.ch12.service.impl.SuperOrderServiceImpl;
public class MultiFactory {
private static final BasicFactory instance;
private OrderService orderService;
private OrderService superOrderService;
static {
instance = new BasicFactory();
}
public static BasicFactory getInstance() {
return instance;
}
public MultiFactory() {
this.orderService = new DefaultOrderServiceImpl();
this.superOrderService = new SuperOrderServiceImpl();
}
public OrderService getOrderService() {
return this.orderService;
}
public OrderService getSuperOrderService() {
return this.superOrderService;
}
}
在这种实现方式下,容器可以调用getSuperOrderService()方法来获得SuperOrderServiceImpl实例。然而,这种方式恰恰否定了工厂的好处。虽然容器没有与特定的实现类耦合,但是与工厂的方法耦合了。另一个缺点是每次添加新实现类的修改都得重新编译,这也很不方便。
另外一个解决方案是在调用getOrderService()方法时把类型传进去,根据传进去的类型来返回这个类型的实现类。这种方式也有很多问题,比如它只能通过类来工作,意味着两个相同类型的实例将无法实现。你将同时发现如果你有许多不同的实现类时,getOrderService()
方法将变得很大很难维护。
相对而言,一个比较好的办法是使用查找方式的方法来让容器选择需要的实现类。所以,不是调用getOrderService(),而是调用getOrderService("someKey")。但即使如此,问题还是存在,为了使得足够灵活,每个容器必须使用特定的key来获取实现,key将变得很多很难维护。而且当需要更新实现时,仍然得修改代码并编译。问题的症结在于容器必须自己去决定使用哪个实现类,所以无论如何,传统的工厂类都会遇到问题。
多个实例模型支持
另一个问题是我们的容器同时需要多个实现的实例。这个问题和刚才的一样,问题在于容器必须自己去决定使用哪个实现类。对返回类型都无法支持,对于返回的实例数量当然也是没有办法支持的。
基于Spring的接口驱动设计
Spring的接口驱动设计对应用程序开发影响很大。因为它能统一管理所有的容器,你压根就不需要担心如何去设计工厂类来获得实现。
在表面看来,当你用spring构建一个接口驱动的程序时,最大的好处是你不用写大量冗余的代码。只需通过一些配置即可。其实,这都归功于spring的依赖注入机制。凭借它,spring去除了容器对于实现类的依赖,只要让容器运行spring注入即可。
依赖注入即是说spring能够提供一个实现类的实例给任何容器,同时不需要在容器中编写任何代码。这是因为spring可以自由的管理任何实现实例的完整生命周期。总之就是spring有我们需要的所有接口驱动设计所必须的特征,从而使我们从如何获得接口实现的问题中得以脱身。
下一节:基于Spring的应用程序的设计和实现(构建领域对象模型) http://wsjjasper.iteye.com/blog/1574590