一个典型的降低耦合的方法是通过interface来隐藏实现细节,因此,实际的类可以进行更改而不影响到客户端。例如你想创建一个Quest interface:
package com.springinaction.chapter01.knight;
public interface Quest {
abstract Object embark() throws QuestFailedException;
}
接下来修改HolyGrailQuest实现这个类,embark方法的返回类型和抛出的异常都不一样了。
package com.springinaction.chapter01.knight;
public class HolyGrailQuest implements Quest{
public HolyGrailQuest(){}
public Object embark() throws GrailFailedException{
//Do what ever it means to embark a quest
return new HolyGrail();
}
}
在KnightOfRoundTable中修改:
private Quest quest;
public Object embarkOnQuest() throws GrailFailedException{
return quest.embark();
}
同样的,KnightOfRoundTable也修改实现一个接口
package com.springinaction.chapter01.knight;
public interface Knight {
Object embarkOnQuest() throws GrailFailedException;
}
把实现隐藏在接口后面是很好的一步,但很多开发者迷惑,如何得到Quest的实例?例如,看看上面的KnightOfRoundTable修改。
KnightOfRoundTable通过Quest接口得到了一个quest。但还是得到了个具体的Quest类型。这没有比以前好多少,KnightOfRoundTable还是跟HolyGrailQuest粘合在一起。
给予和拿来
这时候你会问,是否一个knight要负责得到一个quest,或是否一个knight得到一个embark(上船,为什么?)的quest??
看下面的修改:(set方法)
package com.springinaction.chapter01.knight;
public class KnightOfRoundTable implements Knight{
private String name;
private Quest quest;
public KnightOfRoundTable(String name) {
this.name = name;
}
public Object embarkOnQuest() throws GrailFailedException{
return quest.embark();
}
public void setQuest(Quest quest){
this.quest = quest;
}
}
现在knight被赋予了一个quest,而不是去得到一个quest。KnightOfRoundTable不负责去获取quest,且由于它只通过interface知道quest,你可以随意给一个quest的类型,不一定是HolyGrailQuest。所以在测试时候,你可以赋值一个假的quest给knight。
这就是DI的含义了:合作关系的对象间的关联,被从对象本身中分离出来了。
把一个quest插入到knight中
现在你可以随意把一个quest放入到KnightOfRoundTable中,但你如何制定一个特定的quest放入其中呢?
在程序组件间创建联系好象是电工配线一样。在Spring中,有很多方法把对象搭配在一起,但最普通的方法是通过xml文件。下面就是一个简单的Spring配置文件knight.xml,向KnightOfRoundTable注入了一个quest(HolyGrailQuest).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="quest" class="com.springinaction.chapter01.knight.HolyGrailQuest"/>
<bean id="knight" class="com.springinaction.chapter01.knight.KnightOfRoundTable">
<constructor-arg value="Bedivere"></constructor-arg>
<property name="quest" ref="quest"></property>
</bean>
</beans>
这就是个简单的方法把类搭配在一起。现在不要在意细节。第二章会解释更多。
现在我们声明了knight和quest之间的关系,现在可以运行了。
看看运行一下
在Spring程序中,一个BeanFactory负责载入bean的定义并且把它们绑定在一起的。因为定义写在xml文件中,所以XmlBeanFactory需要用在这里。下面就是这个例子:
package com.springinaction.chapter01.knight;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class KnightApp {
public static void main(String[] args){
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("knight.xml"));
Knight knight = (Knight)factory.getBean("knight");
knight.embarkOnQuest();
}
}
Main中第一行是读取xml配置文件,第二行是从factory中得到knight对象的实例,第三行是向knight发送quest(原句没搞懂)。
一旦程序得到了knight的对象的实例,它就调用embarkOnQuest方法去抛弃了knight的冒险。注意,程序不知道quest和knight做什么,它只知道通过xml文件来把相应的quest类型注入到knight中去。
1.3.3 企业级程序中应用DI
假设我们的系统是在线购物系统。系统中有一个订单系统组件,控制订单的一切活动。下图示例了几种web层访问这个组件的方式。
直接初始化后访问是最简单的(第一个方法)。除了直接把web层同服务组件绑定在一起,它还导致浪费了创建的对象,当组件共享时,仅仅一个无状态的单例就够了。
第二个方法是ejb2.x的。
第三个方法是ejb3.x的。
第四个方法是不论是否使用ejb,你可以选择通过一个什么东西隐藏调用细节。等等等等。
这些方法的主要问题是web层对于如何得到它自己的依赖关系关心的太多了。它知道太多的系统实现了。
如果了解太多的系统实现就需要大量的耦合代码来实现,那么就意味着知道越少的系统实现,代码就越松耦合。看上图,就是Checkout组件被注入了OrderService组件,而不是去得到它。下面的代码就是如何实现这种DI:
private OrderService orderService;
public void doRequest(HttpServletRequest request) {
Order order = createOrder(request);
orderService.createOrder(order);
}
public void setOrderService(OrderService orderService){
this.orderService = orderService;
}
现在看代码,orderService是通过setOrderService方法注入到类中的。Web组件不用知道也关心orderService是从哪里来的。它可以通过Spring或者是直接调用setOrderService方法来注入进去。它也不用管OrderService是如何实现的-它只需要知道它通过的是OrderService接口。通过DI,你的程序不用为了得到各种对象来繁忙,只需要关注自己的任务了,需要的时候,依赖关系自然可以用到了。
DI是松散耦合的福音,操。让你的程序可以尽可能的短。不过,现在你只知道了Spring容器和DI的表面的东西,第二和三章,你将会知道更多的方法通过Spring容器来联系对象。
DI是Spring通过POJO类进行松耦合的唯一技术。AOP提供了另外一个能力来松耦合,它提供了把横向的程序级别的功能从对象里分离的技术,就像安全和事务。下节看一下aop的基础。