为什么要用IOC,详见
1.通常编程与测试方法
一个客户对象依赖于一个服务接口。该服务接口可以提供任何服务,我们把它称为Service。
public interface IService {
void go();
}
public class ServiceImpl implements IService {
public void go() {
...
}
}
单元测试的伪服务对象。
public class MockService implements IService {
private boolean gone = false;
public void go() {
gone = true;
}
public boolean isGone() {
return gone;
}
}
使用IOC之前,最常用的是工厂模式。除了服务接口之外,你还有一个既可以向客户提供服务对象,也可以向测试程序传递伪服务对象的工厂类。在这里我们会将服务实现为一个单件对象,以便让示例尽量简化。
public class ServiceFactory {
private ServiceFactory() {}
private static Service instance = new ServiceImpl();
public static Service getInstance() {
return instance;
}
public static void setInstance(Service service) {
instance = service;
}
}
客户程序每次需要服务对象时就直接从工厂获取。
public class Client {
public void go() {
Service service = ServiceFactory.getInstance();
service.go();
}
}
客户程序的单元测试代码必须将一个伪服务对象传入工厂,同时要记得在测试后清理。在我们这个简单的例子里,这不算什么难事儿。但当你增加了越来越多的客户和服务代码后,所有这些伪代码和清理代码会让单元测试的开发一团糟。此外,如果你忘记在测试后清理,其他测试可能会得到与预期不符的结果。更糟的是,测试的成功与失败可能取决于他们被执行的顺序。
public void testClient() {
Service previous = ServiceFactory.getInstance();
try {
final MockService mock = new MockService();
ServiceFactory.setInstance(mock);
Client client = new Client();
client.go();
assertTrue(mock.isGone());
}
finally {
ServiceFactory.setInstance(previous);
}
}
工厂的API把我们限制在了单件这一种应用模式上。即便 getInstance() 可以返回多个实例, setInstance() 也会束缚我们的手脚。转换到非单件模式也意味着转换到了一套更复杂的API。
2.手工依赖注入
依赖注入模式的目标之一是使单元测试更简单。我们不需要特殊的框架就可以实践依赖注入模式。依靠手工编写代码,你可以得到该模式大约80%的好处。
当上例中的客户代码向工厂对象请求一个服务时,根据依赖注入模式,客户代码希望它所依赖的对象实例可以被传入自己。也就是说:不要调用我,我会调用你。
public class Client {
private final Service service;
public Client(Service service) {
this.service = service;
}
public void go() {
service.go();
}
}
这让我们的单元测试简化了不少。我们可以只传入一个伪服务对象,在结束后也不需要多做什么。
public void testClient() {
MockService mock = new MockService();
Client client = new Client(mock);
client.go();
assertTrue(mock.isGone());
}
3.用Guice实现依赖注入
文件名 说明
HelloGuice.java 业务逻辑 接口 定义文件
HelloGuiceImpl.java 业务逻辑 接口实现 文件
HelloGuiceModule.java 该文件必须 实现com.google.inject.Module接口
TestGuice.java 测试文件
HelloGuice.java
package com.test.guice;
//接口HelloGuice
public interface HelloGuice {
public void sayHello();
}
HelloGuiceImpl.java
package com.test.guice;
//接口的实现类HelloGuiceImpl
public class HelloGuiceImpl implements HelloGuice {
public void sayHello() {
System.out.println("Hello Guice!");
}
}
HelloGuiceModule.java
package com.test.guice;
import com.google.inject.Binder;
import com.google.inject.Module;
public class HelloGuiceModule implements Module {
public void configure(Binder binder) {
//用于告诉 Guice 各个接口对应的实现。
binder.bind(HelloGuice.class).to(HelloGuiceImpl.class);
}
}
TestGuice.java
package com.test.guice;
import junit.framework.TestCase;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class TestGuice extends TestCase {
public void testHelloGuice() {
//使用 Injector 类启动 Guice, 在程序中创建注入器
Injector injector = Guice.createInjector(new HelloGuiceModule());
HelloGuice helloGuice = injector.getInstance(HelloGuice.class);
helloGuice.sayHello();
}
}
运行TestGuice,打印出:
Hello Guice!
4.使用Java Annotation标注来替代显式地绑定对象
如果你需要一个接口来简化单元测试,而你又不介意编译时的依赖,你可以直接从你的接口指向一个缺省的实现。
我们可以直接为HelloGuice加上@ImplementedBy注释,而省略掉对com.google.inject.Module的实现。
HelloGuice.java
package com.test.guice;
import com.google.inject.ImplementedBy;
@ImplementedBy(HelloGuiceImpl.class)
public interface HelloGuice {
public void sayHello();
}
TestGuice.java
package com.test.guice;
import junit.framework.TestCase;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class TestGuice extends TestCase {
public void testHelloGuice() {
//Injector injector = Guice.createInjector(new HelloGuiceModule());
Injector injector = Guice.createInjector();
HelloGuice helloGuice = injector.getInstance(HelloGuice.class);
helloGuice.sayHello();
}
}
HelloGuiceModule.java不再需要。其余的文件内容不变。
运行TestGuice,打印出:Hello Guice!
缺省情况下,Guice 每次都注入一个新的实例。如果你想指定不同的作用域规则,你也可以对实现类进行标注。
@Singleton
package com.test.guice;
//接口的实现类HelloGuiceImpl
public class HelloGuiceImpl implements HelloGuice {
public void sayHello() {
System.out.println("Hello Guice!");
}
}
参考文献:
1.用 Guice 写 Java. http://hi.baidu.com/banseon/blog/item/e11a7523b6c003519822ed9e.html
2.Google Guice范例解说之使用入门. http://hi.baidu.com/kaisep/blog/item/16d4ce09f0195685d0581b3f.html
阅读(1728) | 评论(0) | 转发(0) |