2013-02-19 71 views
2

假设我的类有一个获取资源的方法start()和释放资源的stop()方法。类的start方法可以调用成员对象的start()方法。如果其中一个成员对象的start()引发异常,我必须确保start()成功的所有成员对象都调用stop()。在Java中编写类似展开的清理代码

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中,我可以轻松地使用函数底部的清除代码和“goto”跳转到适当的位置,但是Java没有转到。请注意,不允许在未启动()的对象上调用stop()ed - 我正在查找与上面的代码完全等效但代码更短的代码。

到目前为止,我已经来到了唯一的解决办法是使用布尔记住什么开始,像这样:

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

当您在非启动资源上调用“stop”时会发生什么? – assylias 2013-02-19 14:11:07

+0

我刚才提到过“不允许在未启动()ed的对象上调用stop()”。在start()和stop()函数中可能有assert()。 – 2013-02-19 14:12:34

+2

使stop()方法更加健壮,并随时调用它。 – 2013-02-19 14:12:42

回答

1

虽然我很欣赏所给出的所有想法,但我没有发现它们适合广泛使用我的代码。特别是,基于堆栈/列表的方法存在问题,原因有两个:

  1. start()包装程序不允许将参数传递给它所调用的对象的启动方法。
  2. 一切都需要实现像Stoppable这样的接口。这是有问题的,因为该技术需要为外部提供的类和函数工作,可能不会有start()方法,但有些不同。

即使对象没有启动也可以调用stop()方法的想法并不适用于同样的原因 - 接口可能超出了程序员的控制范围。

最后我已经解决了这个问题,我发现它需要最少量的样板。另一个好处是,即使对象没有启动,但实际上也可以调用结果stop()方法(但由于成员的启动和停止功能可能不在程序员的控制范围内,因此这样做没有意义)。

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; 
} 
+0

现在的问题是:**是否安全地读取变量的值:catch块中的状态?** 在C中,异常处理由setjmp/longjmp实现,并且它要求局部变量是易失性的,如果它们在try块内被修改的话。 – 2015-03-01 10:13:47

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()的东西,但我想它有可能他们已经做的。

+0

不要忘记清除'toStop'后全部停止。 – ogzd 2013-02-19 14:18:38

+0

@ogzd - 好点 – Qwerky 2013-02-19 14:21:40

+0

由于a.start()方法的参数必须通过start()包装器传递,因此它实际上并不像写入的那样工作。另外,清理需要以相反的顺序进行。 – 2013-02-19 14:27:17

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(); 
      } 
     } 
    } 
} 
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

+0

是的,我试图避免嵌套,你不得不承认这看起来很丑:) – 2013-02-19 14:24:21