说我有这三层代码:
1.数据库层(ORM)
2. BusinessLogic
3.申请
现在,我按如下方式编写代码:
数据库层:
这主要是对数据库进行CURD操作。
class MyDatabaseLayer {
public int findValue(int k) {
// find v
}
public void insertValue(int k, int v) {
// Insert v
}
}
BusinessLogic:
这保留了调用数据库层并执行操作的实际逻辑。
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 says:构造函数注入很好。但如果我遵循这一点,我将如何实现抽象? Google Guice如何帮助我?
答案 0 :(得分:1)
控制反转所缺少的部分是应用程序层不直接调用构造函数。它使用工厂(IoC容器)来填充构造函数参数。
无论您使用什么工具,guice / spring / picocontainer / singleton-factories,您的应用程序代码应如下所示:
@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;
}
...
}
但在那时,picocontainer,guice和spring可以更好地解决上述SOOO的复杂性。
此外,像spring这样尊重java 6注释的东西意味着你可以做除了构造函数注入之外的事情,如果你有相同基本数据类型的多个配置项(例如字符串),这非常有用。
答案 1 :(得分:1)
请注意,对于Misko所指的可测试性,理想情况下,您希望为MyDatabaseLayer
,MyBusinessLogic
等创建接口,并让构造函数采用这些接口而不是具体类,以便在测试中您可以轻松传递实际上不使用数据库等的虚假实现。
使用Guice,您可以将接口绑定到Module
或Module
中的具体类。然后,您将使用Injector
创建Module
并从Injector
获取一些根对象(例如,您的应用程序对象)。
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
获取对象。