假设我有一个Java类
abstract class Base {
abstract void init();
...
}
我知道每个派生类在构造之后都必须调用init()
。当然,我可以简单地在派生类的构造函数中调用它:
class Derived1 extends Base {
Derived1() {
...
init();
}
}
class Derived2 extends Base {
Derived2() {
...
init();
}
}
但是这会打破“不要重复自己”的原则(而且会有很多Base
的子类)。当然,init()
调用不能进入Base()
构造函数,因为它会过早执行。
任何想法如何绕过这个问题?我也很高兴看到Scala解决方案。
更新:以下是工厂方法方法的通用版本:
interface Maker<T extends Base> {
T make();
}
class Base {
...
static <T extends Base> T makeAndInit(Maker<T> maker) {
T result = maker.make();
result.init();
return result;
}
}
更新2:这个问题基本上是“你如何使用模板方法为构造函数”?答案似乎是,“你可以,但这是一个坏主意”。所以我可以改为使用模板工厂(模板方法+抽象工厂)。
答案 0 :(得分:11)
避免这种情况。如果你这样做,任何扩展你的DerivedX
类的类都可能决定调用init()
,从而使对象保持不一致状态。
一种方法是让您的类的客户端手动调用init()
方法。如果有任何需要初始化的方法,则调用initialized
字段并抛出IllegalStateExcepion
。
更好的方法是使用静态工厂方法而不是构造函数:
public Derived2 extends Base {
public static Derived2 create() {
Derived2 instance = new Dervied2();
instance.init();
return instance;
}
}
更新:正如您在更新中建议的那样,您可以将Builder传递给静态工厂方法,该方法将调用实例上的init()
。如果您的子类很少,我认为这是一个过度复杂的。
答案 1 :(得分:10)
init()
会发生什么?很可能一个更好的设计可以完全消除该方法,或者至少放松它在子类的构造函数之后执行的要求。确保init()
在构造函数完成之前不会使构造中的对象对任何其他线程可见,因为这会产生并发错误。
作为(丑陋的)替代方案,可以通过子类将抽象方法实现为伪构造函数:
abstract class Base {
Base() {
ctor();
init();
}
abstract void ctor();
abstract void init();
}
答案 2 :(得分:6)
除了Bozho的recommendation之外,应用程序容器非常适合这项任务。
使用init()
注释标记您的javax.annotation.PostConstruct
方法,正确配置的EJB或Spring容器将在依赖注入完成后但在应用程序可以使用该对象之前执行该方法。 / p>
示例方法:
@PostConstruct
public void init() {
// logic..
}
在企业应用程序中,您可以使用init()
方法打开资源,例如文件系统。此初始化可以抛出异常,不应该从构造函数中调用。
答案 3 :(得分:2)
如果Java拥有它,我们就不会在野外看到所有这些init()方法调用。
“环绕子构造函数的东西” - 在纯java中无法完成。太糟糕了,因为可能会有非常有趣的应用程序,尤其是匿名类+实例初始化块。
工厂和容器 - 当本地new
不能完成工作时,它们会很有用;但这很简单,很无聊,也不适用于匿名课程。
答案 4 :(得分:1)
或者使用spring ...您可以<beans default-init-method="init">
,请参阅Default initialization and destroy methods。
答案 5 :(得分:1)
如果由于某种原因你不喜欢使用工厂,你可以使用以下技巧:
trait RunInit {
def init():Unit
init()
}
class Derived1 extends Base with RunInit {
def init() = println("INIT'ing!")
}
这将在Derived1构造函数/ body之前运行init()。