2

假设我的类有一个方法 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;
}

注意清理代码的二次增长。进行清理的最佳方法是什么(最少的代码)?在 CI 中,可以通过函数底部的清理代码和“goto”轻松地做到这一点,以跳转到适当的位置,但是 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;
    }
}

我知道“finally”和“try-with-resources”,但这些似乎都不适用这里,因为如果没有例外,资源不应该被释放。

PS这不是关于我使用异常或我的程序设计的问题。这特别是关于在初始化代码失败的情况下进行清理。

4

4 回答 4

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()这需要您通过接口或子类化开始拥有某种共同点的东西,但我想他们可能已经这样做了。

于 2013-02-19T14:17:07.207 回答
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 ();
            }
        }
    }
}
于 2013-02-19T14:18:55.107 回答
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其他人建议的方法。

于 2013-02-19T14:22:15.697 回答
1

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

  1. start() 包装器不允许将参数传递给它调用的对象的 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;
}
于 2013-02-19T23:16:14.650 回答