java - 如何在 Java 中概括方法调用(以避免代码重复)

标签 java methods inner-classes anonymous-class code-duplication

我有一个进程需要调用一个方法并返回它的值。但是,根据情况,此过程可能需要调用几种不同的方法。如果我可以将方法及其参数传递给进程(就像在 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 的每个公共(public)方法都实例化了一个匿名内部类,它是 RetryableZooKeeperAction 内部类的子类,并且它重写了 action() 方法。局部变量(足够奇怪)隐式传递给 action() 方法,这是可能的,因为它们是最终的。

最后,这种方法确实有效,并且确实消除了重试逻辑的重复。但是,它有两个主要缺点:(1) 每次调用方法时都会创建一个新对象,(2) 难看且难以阅读。此外,我还必须解决具有无效返回值的“删除”方法。

那么,这是我的问题:在 Java 中有没有更好的方法来做到这一点?这不可能是一项完全不常见的任务,其他语言(如 Python)通过允许传递方法使其变得更容易。我怀疑可能有一种方法可以通过反射(reflection)来做到这一点,但我一直无法全神贯注。

最佳答案

这似乎(至少对我而言)是“正确的”Java 式(或 Java 中“Pythonic”的任何类似物)重构。您已经正确地使用了模板方法模式,并且将 final 变量传递到内部匿名子类中是正确的,正如内部类的设计者所期望的那样。您保留了静态类型并且很好地使用了泛型。

使用反射的解决方案确实可行,但它确实牺牲了一些静态类型的细节,并且您的代码将不得不捕获一些调用方法附带的已检查异常,从而增加了一些困惑。反射被高估了,恕我直言,这里没有必要。我们倾向于在 Java 中更多地使用它来进行检测,而不是使代码更易于阅读。如果你想干净地传递方法,请等待 Java 8!

此外,我不认为您的代码不可读。专业的 Java 程序员应该能够阅读本文。内部类就是为这种类型的东西而存在的。也就是说,可以重构“再一步”并将现在的匿名类命名为本地类(当然仍然在你的方法中)所以你的调用run() 并不难找到。除此之外,我没有真正的重构建议。

关于java - 如何在 Java 中概括方法调用(以避免代码重复),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11355339/

相关文章:

Java Object.wait(long, long) 实现与文档不同

C#,是否可以在不创建新对象变量的情况下重铸对象并访问方法和属性

javascript - 如何在 javascript 中使用嵌套函数作为生成器(使用 "inner"产量)

java - 覆盖 Java 单元测试中的异常语句

java - 运行 JNLP 卡在验证应用程序上

Javascript setInterval 只运行一次

Ruby 方法范围?

Java匿名内部类

Java 编译器无法识别静态内部类

java - 使用有问题的 wsdl 的服务 - 意外的元素异常