Java 使用构造函数注入正确设计为可测试性而构建的类
假设我有这3层代码:Java 使用构造函数注入正确设计为可测试性而构建的类,java,inversion-of-control,class-design,guice,testability,Java,Inversion Of Control,Class Design,Guice,Testability,假设我有这3层代码: 1.数据库层(ORM) 2.业务逻辑 3.应用 现在,我编写代码如下: 数据库层: 这主要是通过数据库执行CURD操作 class MyDatabaseLayer { public int findValue(int k) { // find v } public void insertValue(int k, int v) { // Insert v } } 业务逻辑: 这包含了调用数据库层和执行任务的
1.数据库层(ORM)
2.业务逻辑
3.应用 现在,我编写代码如下:
这主要是通过数据库执行CURD操作
class MyDatabaseLayer {
public int findValue(int k) {
// find v
}
public void insertValue(int k, int v) {
// Insert v
}
}
这包含了调用数据库层和执行任务的实际逻辑
class MyBusinessLogic {
private MyDatabaseLayer dbLayer;
public MyBusinessLogic(MyDatabaseLayer dbLayer) {
this.dbLayer = dbLayer;
}
public int manipulateValue(int k) {
dbLayer.findValue(k);
//do stuff with value
}
}
这将调用业务逻辑并显示数据
MyBusinessLogic logic = new MyBusinessLogic(new MyDatabaseLayer ()); //The problem
logic.manipulateValue(5);
Misko Hevery:构造函数注入很好。但如果我遵循这一点,我将如何实现抽象?Google Guice如何帮助我呢?控制反转缺少的部分是应用层不直接调用构造函数。。它使用工厂(IoC容器)来填充构造函数参数 无论您使用什么工具,guice/spring/picocontainer/singleton Factorys,您的应用程序代码应该如下所示:
@Controller
class MyController {
@Resource // Some container knows about this annotation and wires you in
MyBusinessLogic myBusinessLogic;
@RequestMethod("/foo/bar.*")
public MyWebResponse doService(Response resp, long id, String val) {
boolean worked = myBusinessLogic.manipulatevalue(id, val);
return new MyWebResponse(worked);
}
}
注意,myBusinessLogic可以通过几种方式注册—java的@Resource、MyBusinessLogicFactory.getMyBusinessLogic()、guice.get(myBusinessLogic.class)等
穷人的解决办法是:
package foo;
class MyBusinessLogicFactory {
static volatile MyBusinessLogic instance; // package-scoped so unit tests can override
public static MyBusinessLogic getInstance() {
if (instance == null) {
synchronized(MyBusinessLogicFactory.class) {
instance = new MyBusinessLogic(MyDatabaseLayerFactory.getInstance());
}
}
return instance;
}
}
// repeat with MyDatabaseLayerFactory
注意,上面的单例模型非常不推荐,因为它没有作用域。您可以将上面的内容封装在上下文中,例如
class Context {
Map<Class,Object> class2Instance = new ConcurrentHashMap<>();
public <T> T getInstance(Class<T> clazz) {
Object o = class2Instance.get(clazz);
if (o == null) {
synchronized(this) {
o = class2Instance.get(clazz);
if (o != null) return (T)o;
o = transitivelyLoadInstance(clazz); // details not shown
for (Class c : loadClassTree(clazz)) { // details not shown
class2Instance.put(c, o);
}
}
}
return (T)o;
}
...
}
类上下文{
Map class2Instance=新的ConcurrentHashMap();
公共T getInstance(类clazz){
对象o=class2Instance.get(clazz);
如果(o==null){
已同步(此){
o=class2实例get(clazz);
如果(o!=null)返回(T)o;
o=过渡状态(clazz);//未显示详细信息
对于(c类:loadClassTree(clazz)){//未显示详细信息
第2类惯性摆(c,o);
}
}
}
返回(T)o;
}
...
}
但在这一点上,picocontainer、guice和spring可以更好地解决上述问题的复杂性
此外,spring等支持java 6注释的东西意味着您可以执行构造函数注入以外的操作,如果您有相同基本数据类型(例如字符串)的多个配置项,则构造函数注入非常有用。请注意,对于Misko提到的可测试性,理想情况下您希望为
MyDatabaseLayer
创建接口,MyBusinessLogic
等,并让构造函数使用这些接口而不是具体的类,以便在测试中,您可以轻松地传递实际上不使用数据库的伪实现,等等
使用Guice,可以将接口绑定到模块
或模块
s中的具体类。然后,您将使用这些模块创建注入器,并从注入器中获取一些根对象(例如,您的应用程序对象)
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(MyDatabaseLayer.class).to(MyDatabaseLayerImplementation.class);
// etc.
});
MyApplicationLayer applicationLayer = injector.getInstance(MyApplicationLayer.class);
在MyApplicationLayer
中,您将注入业务逻辑:
@Inject
public MyApplicationLayer(MyBusinessLogic logic) {
this.logic = logic;
}
这当然是一个非常简单的例子,你可以做很多更复杂的事情。例如,在web应用程序中,您可以使用Guice Servlet在Servlet上使用构造函数注入,而不是在创建对象后直接从Injector
获取对象。是的,我有接口。我只是用这个例子来简化事情。你能给我指出一些复杂示例的参考资料吗?我会看看GUI,包括绑定、作用域、扩展等。顺便说一句,我正在处理一些遗留代码,我无法为MyDatabaseLayer
提供接口。从Guice的角度来看,这会是一个问题吗?在扩展AbstractModule的类中的一个绑定中,我需要:bind(MyDatabaseLayerImplementation.class).to(MyDatabaseLayerImplementation.class)代码>因为我没有接口。这是允许的吗?您可能无法更改数据库类以实现接口,但能否创建一个接口和一个委托给实际数据库类的实现?对于第二个问题,如果MyDatabaseLayerImplementation
是一个具体的类,那么您根本不需要绑定它。。。不过,如果您愿意,您可以通过执行bind(MyDatabaseLayerImplementation.class)
“但是在这一点上,picocontainer、guice和spring可以更好地解决上述问题的复杂性。”。你能详细说明那部分吗?