我正在尝试模拟以下抽象类,该类旨在只启用一个延迟初始化,而不使用逻辑语句。为了简单起见,我忽略了线程安全所必需的同步元素。
abstract class Thunk<T>
{
private boolean initiated = false;
private T value;
public T get()
{
if(!initiated) // not using (value == null)
{
value = compute();
initiated = true;
}
return value;
}
abstract protected T compute();
}
以下抽象类的实例是否会被子级多次入侵以初始化同一变量?
abstract class Thunk<T>
{
private T value;
private Computer<T> computer;
public Thunk()
{
computer = new Computer<T>(this);
}
public T get()
{
value = computer.getValue();
return value;
}
abstract protected T compute();
private class Computer<T>
{
private static final String TAG = "Computer";
private Thunk<T> thunk;
private T value;
private Computer<T> computer;
public Computer(Thunk<T> thunk)
{
Log.d(TAG, "constructed");
this.thunk = thunk;
computer = this;
}
public T getValue()
{
Log.d(TAG + ".getValue()", "");
value = computer.computeValue();
return value;
}
protected T computeValue()
{
Log.d(TAG + ".computeValue()", "");
value = thunk.compute();
computer = new DumbComputer<T>(thunk, value);
return value;
}
//this is for maximal encapsulation
private class DumbComputer<T> extends Computer<T>
{
private static final String TAG = "DumbComputer";
private T value;
public DumbComputer(Thunk<T> thunk, T value)
{
super(thunk);
Log.d(TAG + ".contructed()", "booki");
this.value = value;
}
//overriding so that value will be calculated only once.
@Override
protected T computeValue()
{
Log.d(TAG + ".computeValue()", "");
return value;
}
}
}
}
是,通过重写get
方法。
要修复此问题,可以将get
设置为final
方法。这将防止重写,并为您提供类似于singleton的行为。
请注意,您编写的代码不是线程安全的。
您可以通过制作方法synchronized
来实现线程安全(不要担心性能,直到你知道你出了问题,并且方法是热点,因为慢速正确的代码比快速错误的代码好,JVM非常善于优化锁。如果你发现这个类的特定锁过热,你可以使用一些技巧来加速它…但现在还不用担心)
同样值得指出的是,如果你想要最好的singleton惰性init,那么可以使用惰性init的资源持有者内部类模式(不适用于你的用例,因为这个类需要。它只用于singleton)。
更新(回复评论,因为评论不支持格式化)
这样做:
abstract class Thunk<T>
{
private boolean initiated = false;
private T value;
public synchronized final T get()
{
if(!initiated) // not using (value == null)
{
value = compute();
initiated = true;
}
return value;
}
abstract protected T compute();
}
这是可能工作的最简单的代码。甚至不要梦想尝试"改进"那个代码。它是可以改进的,但根据类的使用方式,改进会有所不同,而且改进的复杂性会掩盖代码的意图。从最简单的可行方法开始,然后继续。
保持简单愚蠢的
不要解决你还没有的问题
模式
public final void f() {
...
X x = ...;
g(x);
...
}
abstract protected void g(X x);
在合同编程中非常有用:
- 施加行为(f体),以及
- 以提供本地上下文(x)
行为通常是通过保持一个状态(如initiated
)来实现的。所以,是的,这对于懒惰的评估来说是可以的。尽管懒惰的评估可以在现场级别上实现,例如通过罕见的宝石Future<>
。
您的第二个示例(可能)没有按预期工作,因为每次调用Thunk.get
时都会创建一个新的DumbComputer
。你可以按照以下方式实现你的目标(但我认为这不是一个好的设计,我真的看不出与更简单的解决方案相比优势在哪里):
abstract class Thunk<T> {
T value;
Computer<T> computer;
protected abstract T doCompute ();
private interface Computer<T> {
Computer getComputer ();
T compute ();
}
public Thunk<T> () {
// initialize computer with a calculating one
computer = new Computer<T> () {
Computer getComputer () {
// return a dumb computer
return new Computer<T> () {
Computer getComputer () { return this; }
T compute () { return value; }
}
}
T compute () { value = doCompute (); return value; }
};
}
public T getValue () {
T v = computer.compute (); computer = computer.getComputer (); return v;
}
}