假设我的类有一个获取资源的方法start()和释放资源的stop()。类的start方法可以调用成员对象的start()方法。如果其中一个成员对象的start()抛出异常,我必须确保为start()成功的所有成员对象调用stop()。
class X {
public X ()
{
a = new A();
b = new B();
c = new C();
d = new D();
}
public void start () throws Exception
{
try {
a.start();
} catch (Exception e) {
throw e;
}
try {
b.start();
} catch (Exception e) {
a.stop();
throw e;
}
try {
c.start();
} catch (Exception e) {
b.stop();
a.stop();
throw e;
}
try {
d.start();
} catch (Exception e) {
c.stop();
b.stop();
a.stop();
throw e;
}
}
public void stop ()
{
d.stop();
c.stop();
b.stop();
a.stop();
}
private A a;
private B b;
private C c;
private D d;
}
请注意清理代码的二次增长。清理的最佳方法(最少量的代码)是什么?在C中,我可以使用函数底部的清理代码轻松地执行此操作,并“转到”以跳转到适当的位置,但是Java没有goto。请注意,不允许在未启动()编辑的对象上调用stop() - 我正在寻找与上述完全相同但更短的代码。
到目前为止,我遇到的唯一解决方案是使用布尔值来记住启动的内容,如下所示:
public void start () throws Exception
{
boolean aStarted = false;
boolean bStarted = false;
boolean cStarted = false;
boolean dStarted = false;
try {
a.start();
aStarted = true;
b.start();
bStarted = true;
c.start();
cStarted = true;
d.start();
dStarted = true;
} catch (Exception e) {
if (dStarted) d.stop();
if (cStarted) c.stop();
if (bStarted) b.stop();
if (aStarted) a.stop();
throw e;
}
}
我知道“终于”和“尝试使用资源”,但这些都不适用于此,因为如果没有例外,就不应该释放资源。
P.S。这不是关于我使用异常或程序设计的问题。这特别是关于初始化代码失败时的清理。
答案 0 :(得分:4)
如何将你开始的东西添加到堆栈中,然后当你需要停止东西时,将所有东西从堆栈中弹出并停止它。
private Deque<Stoppable> toStop = new ArrayDeque<Stoppable>();
public void start() throws Exception {
try {
start(a);
start(b);
start(c);
start(d);
} catch (Exception e) {
stop();
throw e;
}
}
private void start(Stoppable s) throws Exception {
s.start();
toStop.push(s);
}
public void stop() {
while (toStop.size > 0) {
toStop().pop().stop();
}
}
这需要你通过接口或子类开始拥有某种常见stop()
的东西,但我想他们可能已经这样做了。
答案 1 :(得分:2)
public class X
{
private final List <Stoppable> stoppables =
new ArrayList <Stoppable> ();
private void start (StartStoppable x)
{
x.start ();
stoppables.add (x);
}
public void startAll ()
{
try
{
start (a);
start (b);
start (c);
start (d);
}
catch (Throwable ex)
{
stopAll ();
ex.printStackTrace ();
}
}
public void stopAll ()
{
for (Stoppable s: stoppables)
{
try
{
s.stop ();
}
catch (Throwable ex)
{
ex.printStackTrace ();
}
}
}
}
答案 2 :(得分:1)
如果您对代码的线性爆炸感到满意,可以使用如下结构的start
方法:
public void start () throws Exception
{
a.start();
try {
b.start();
try {
c.start();
try {
d.start();
} catch (Exception e) {
c.stop();
throw e;
}
} catch (Exception e) {
b.stop();
throw e;
}
} catch (Exception e) {
a.stop();
throw e;
}
}
如果你开始/停止的项目不止一些,请使用List
和其他人一样建议。
答案 3 :(得分:1)
虽然我很欣赏所提出的所有想法,但我发现它们中的任何一个都不适合广泛使用我的代码。特别是,基于堆栈/列表的方法存在问题,原因有两个:
即使对象未启动也只能使stop()可调用的想法不适合同样的原因 - 接口可能不受程序员的控制。
最后我已经解决了这个问题,我发现它需要最少量的样板。另一个好处是,即使未启动对象,也可以实际调用生成的stop()方法(但这不会使方法毫无意义,因为成员的启动和停止功能可能不受程序员的控制)。 / p>
class X {
public X ()
{
a = new A();
b = new B();
c = new C();
d = new D();
}
public void start () throws Exception
{
assert(state == 0);
try {
a.start();
state = 1;
b.start();
state = 2;
c.start();
state = 3;
d.start();
state = 4;
} catch (Exception e) {
stop();
throw e;
}
}
public void stop ()
{
if (state >= 4) d.stop();
if (state >= 3) c.stop();
if (state >= 2) b.stop();
if (state >= 1) a.stop();
state = 0;
}
private int state;
private A a;
private B b;
private C c;
private D d;
}