2

私のクラスに、リソースを取得するメソッド start() とリソースを解放する stop() があるとします。クラスの開始メソッドは、メンバー オブジェクトの start() メソッドを呼び出すことができます。メンバー オブジェクトの 1 つの 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;
}

クリーンアップ コードの 2 次成長に注意してください。クリーンアップを行うための最良の方法 (コード量が最小) は何ですか? CI では、関数の下部にあるクリーンアップ コードと "goto" を使用して適切な場所にジャンプすることでこれを簡単に実行できますが、Java には goto がありません。start() されていないオブジェクトで 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

提供されたすべてのアイデアに感謝していますが、私のコードを広く使用するのに適したものはありません。特に、スタック/リスト ベースのアプローチは、次の 2 つの理由で問題があります。

  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 に答える
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 に答える