在Java中编写类似unwind的清理代码

时间:2013-02-19 14:09:58

标签: java exception goto

假设我的类有一个获取资源的方法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。这不是关于我使用异常或程序设计的问题。这特别是关于初始化代码失败时的清理。

4 个答案:

答案 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)

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

  1. start()包装器不允许将参数传递给它调用的对象的start方法。
  2. 一切都必须实现像Stoppable这样的界面。这是有问题的,因为该技术需要适用于外部提供的类和函数,可能没有start()方法,但有些不同。
  3. 即使对象未启动也只能使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;
    }