3

メソッドを呼び出してその値を返す必要があるプロセスがあります。ただし、状況に応じて、このプロセスで呼び出す必要があるメソッドがいくつかあります。メソッドとその引数を (Python のように) プロセスに渡すことができれば、これは問題ありません。ただし、Javaでこれを行う方法はわかりません。

これが具体的な例です。(この例では Apache ZooKeeper を使用していますが、例を理解するために ZooKeeper について何も知る必要はありません。)

ZooKeeper オブジェクトには、ネットワークがダウンした場合に失敗するいくつかのメソッドがあります。この場合、私は常にメソッドを再試行したいと考えています。これを簡単にするために、ZooKeeper クラスを継承する「BetterZooKeeper」クラスを作成し、そのすべてのメソッドは失敗時に自動的に再試行します。

コードは次のようになります。

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(String path, Watcher watcher) {
    while (true) {
      try {
        return super.exists(path, watcher);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public byte[] getData(String path, boolean watch, Stat stat) {
    while (true) {
      try {
        return super.getData(path, watch, stat);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public void delete(String path, int version) {
    while (true) {
      try {
        super.delete(path, version);
        return;
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }
}

(実際のプログラムには、簡単にするために例から取り出したより多くのロジックと多くのメソッドがあります。)

同じ再試行ロジックを使用していることがわかりますが、引数、メソッド呼び出し、および戻り値の型はすべてメソッドごとに異なります。

コードの重複を排除するために私がしたことは次のとおりです。

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(final String path, final Watcher watcher) {
    return new RetryableZooKeeperAction<Stat>() {
      @Override
      public Stat action() {
        return BetterZooKeeper.super.exists(path, watcher);
      }
    }.run();
  }

  @Override
  public byte[] getData(final String path, final boolean watch, final Stat stat) {
    return new RetryableZooKeeperAction<byte[]>() {
      @Override
      public byte[] action() {
        return BetterZooKeeper.super.getData(path, watch, stat);
      }
    }.run();
  }

  @Override
  public void delete(final String path, final int version) {
    new RetryableZooKeeperAction<Object>() {
      @Override
      public Object action() {
        BetterZooKeeper.super.delete(path, version);
        return null;
      }
    }.run();
    return;
  }

  private abstract class RetryableZooKeeperAction<T> {

    public abstract T action();

    public final T run() {
      while (true) {
        try {
          return action();
        } catch (KeeperException e) {
          // We will retry.
        }
        waitForReconnect();
      }
    }
  }
}

RetryableZooKeeperAction は、関数の戻り値の型でパラメーター化されます。run() メソッドは再試行ロジックを保持し、action() メソッドは実行する必要がある ZooKeeper メソッドのプレースホルダーです。BetterZooKeeper の各パブリック メソッドは、RetryableZooKeeperAction 内部クラスのサブクラスである匿名の内部クラスをインスタンス化し、action() メソッドをオーバーライドします。ローカル変数は (奇妙なことに) 暗黙的に action() メソッドに渡されます。これは、それらが final であるため可能です。

最終的に、このアプローチは機能し、再試行ロジックの重複を排除します。ただし、これには 2 つの大きな欠点があります。(1) メソッドが呼び出されるたびに新しいオブジェクトが作成されること、(2) 見苦しく、読みにくいことです。また、無効な戻り値を持つ「削除」メソッドを回避する必要がありました。

だから、ここに私の質問があります.Javaでこれを行うより良い方法はありますか? これはまったく珍しいタスクではありません。また、他の言語 (Python など) では、メソッドを渡すことができるようにすることで簡単に処理できます。リフレクションを通じてこれを行う方法があるのではないかと思いますが、頭を包むことができませんでした。

4

4 に答える 4

2

これは(少なくとも私には)「正しい」Java風(またはJavaの「Pythonic」に類似したもの)のリファクタリングのようです。テンプレートメソッドパターンを適切に使用し、最終変数を内部の匿名サブクラスに渡すことは、内部クラスの設計者が意図したとおりに正確に行われます。静的型付けを維持し、ジェネリックスをうまく使用しています。

リフレクションを使用した解決策は確かに可能ですが、静的型付けの優れた点をいくらか犠牲にし、コードはメソッドの呼び出しに伴ういくつかのチェック済み例外をキャッチする必要があり、混乱が生じます。反射は過大評価されており、IMHOはここでは必要ありません。Javaでは、コードを読みやすくするよりも、インストルメンテーションに使用する傾向があります。メソッドをきれいに渡したい場合は、Java8を待ってください。

また、あなたのコードが読めないと思います。プロのJavaプログラマーはこれを読むことができるはずです。このタイプのものには内部クラスが存在します。とは言うものの、「もう1つのステップ」をリファクタリングして、現在は匿名クラスであるものをローカルクラスという名前にすることができます(もちろん、メソッド内で)。そのため、の呼び出しrun()を見つけるのはそれほど難しくありません。それ以外に、実際のリファクタリングの提案はありません。

于 2012-07-06T03:29:08.883 に答える
1

記述されたコードは完全に合理的であり、現在のバージョンのJavaではあまり改善できません。閉鎖(まもなく実現する)が役立つでしょう。

頭を包むために別の複雑さを導入するリスクがありますが、この種の包み込みを緩和することは、アスペクト指向プログラミングの主な利点の1つです。Javaで作業していることを考えると、特にAspectJを調べたいと思うかもしれません。

于 2012-07-06T03:34:03.760 に答える
0

すでに提案したように、AOPおよびJavaアノテーションを使用する必要があります。jcabi-aspectsからの既製のメカニズムをお勧めします(私は開発者です):

@RetryOnFailure(attempts = 3, delay = 5)
public String load(URL url) {
  // do the work
}

任意のメソッドに注釈を付けることができ@RetryOnFailure、例外がスローされると、完全停止が成功するまでその呼び出しが数回繰り返されます。

このブログ投稿も読んでください:http ://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html

于 2013-02-03T08:33:13.240 に答える
0

これには、インターフェースとジェネリックの組み合わせが必要だと思います。まず、ジェネリックスを使用して引数を指定し、型を返すことで、呼び出し可能なメソッドの動作を記述するインターフェイスを定義します。

public interface Action<Arg, Return> {
    Return attempt(Arg arg) throws KeeperException;
}

これを使用して、再試行メソッドをジェネリックメソッドとして1回作成できます(ジェネリッククラス全体を作成する必要はありません)。

public <Arg, Return> ReturnType retry(Arg arg, Action<Arg, Return> action) {
    while (true) {
        try {
            return action.attempt(arg);
        } catch (KeeperException e) {
            // We will retry.
        }
        waitForReconnect();
    }
}

次に、引数の各コレクションの代わりにデータ構造を指定する必要があります。アクションの場合、次のexistsようになります。

public class ExistsArgs {
    String path;
    Watcher watcher;
}

このexistsアクションは、voidを返すため、パターンに最も適合しません。もう1つの具体的なタイプが必要です。

public final class Void {
    // nothing class to take the place of void return type
}

public class ExistsAction implements Action<ExistsArgs, Void> {
    private ZooKeeper delegate;
    public ExistsAction(ZooKeeper delegate) {
        this.delegate = delegate;
    }
    public Void attempt(ExistsArgs args) throws KeeperException {
        delegate.exists(args.path, args.watcher);
        return null;
    }
}

ExistsArgs静的内部クラスを作成することは理にかなっているかもしれませんExistsAction。)他のメソッドも同様に処理できます。Action実装がの内部クラスである場合BetterZooKeeperは、コンストラクターとデリゲートメンバー変数を削除し、のBetterZooKeeper.this代わりに使用できますdelegate

于 2012-07-06T03:33:54.540 に答える