我可以通过几种不同的方式初始化复杂对象(注入依赖项和注入成员所需的设置),看起来都很合理,但各有利弊。我将举一个具体的例子:
final class MyClass {
private final Dependency dependency;
@Inject public MyClass(Dependency dependency) {
this.dependency = dependency;
dependency.addHandler(new Handler() {
@Override void handle(int foo) { MyClass.this.doSomething(foo); }
});
doSomething(0);
}
private void doSomething(int foo) { dependency.doSomethingElse(foo+1); }
}
如您所见,构造函数执行了3项操作,包括调用实例方法。我被告知从构造函数中调用实例方法是不安全的,因为它绕过了编译器对未初始化成员的检查。即我可以在设置doSomething(0)
之前调用this.dependency
,这可能已经编译但没有工作。重构这个的最佳方法是什么?
使doSomething
为静态并明确传入依赖项?在我的实际情况中,我有三个实例方法和三个成员字段,它们都相互依赖,所以这似乎是很多额外的样板,使这三个都是静态的。
将addHandler
和doSomething
移至@Inject public void init()
方法。虽然使用Guice是透明的,但它需要任何手动构造才能确保调用init()
,否则如果有人忘记该对象将无法完全发挥作用。此外,这暴露了更多的API,这两者似乎都是坏主意。
包装嵌套类以保持依赖关系以确保其行为正常而不暴露其他API:
class DependencyManager { private final Dependency dependency; public DependecyManager(Dependency dependency) { ... } public doSomething(int foo) { ... } } @Inject public MyClass(Dependency dependency) { DependencyManager manager = new DependencyManager(dependency); manager.doSomething(0); }这会从所有构造函数中提取实例方法,但会生成一个额外的类层,当我已经有内部和匿名类(例如该处理程序)时,它会变得混乱 - 当我尝试这个时,我被告知要移动{{1}到一个单独的文件,这也是令人反感的,因为它现在是多个文件来做一件事。
那么处理这种情况的首选方法是什么?
答案 0 :(得分:9)
遗传也很糟糕。如果在链中调用构造函数来实例化类的子类,则可以调用在子类中重写的方法,并依赖于在子类构造函数运行之前未建立的不变量。
答案 1 :(得分:8)
Effective Java中的Josh Bloch建议使用静态工厂方法,尽管我找不到像这样的情况的任何参数。但是,Java Concurrency in Practice中存在类似的情况,专门用于防止从构造函数中泄漏对this
的引用。应用于这种情况,它看起来像:
final class MyClass {
private final Dependency dependency;
private MyClass(Dependency dependency) {
this.dependency = dependency;
}
public static createInstance(Dependency dependency) {
MyClass instance = new MyClass(dependency);
dependency.addHandler(new Handler() {
@Override void handle(int foo) { instance.doSomething(foo); }
});
instance.doSomething(0);
return instance;
}
...
}
但是,这可能不适用于您使用的DI注释。
答案 2 :(得分:4)
您需要注意在构造函数中使用实例方法,因为类尚未完全构造。如果被调用的方法使用的是尚未初始化的成员,那么就会发生坏事。
答案 3 :(得分:0)
是的,它实际上是非法的,它甚至不应该编译(但我相信它确实存在)
考虑构建器模式(并且倾向于不可变,在构建器模式术语中意味着您不能两次调用任何setter并且在对象被“使用”之后无法调用任何setter - 在此处调用setter point应该可能抛出运行时异常。)
你可以在一个名为“Effective Java Reloaded:This Time It for Real”的幻灯片演示文稿中找到Joshua Bloch关于(新)Builder模式的幻灯片,例如:
答案 4 :(得分:0)
您可以使用一个静态方法来获取依赖关系并构造并返回一个新实例,并标记构造函数Friend。我不确定朋友是否存在于java中(是否受到包保护。)这可能不是最好的方法。您还可以使用另一个作为Factory的类来创建MyClass。
编辑:哇另一个帖子刚建议同样的事情。看起来你可以在Java中使构造函数成为私有的。你不能在VB.NET中做到这一点(不确定C#)......非常酷......