Предотвращение дублирования кода при проверке ответов по умолчанию

У меня есть программа на Java, которая вызывает внешний API (RealApi в приведенном ниже коде), и иногда я хочу избежать вызова этого API и вместо этого возвращать предварительно созданные ответы (сгенерированные FakeApi).

Итак, в итоге я продублировал эту конструкцию в большинстве своих методов:

public Type1 m1(String s) {
    try {
        Type1 r = FakeApi.m1(s);
        if (r != null) {
            return r;
        }
    } catch (Exception e) {
        // log error
    }

    return RealApi.m1(s);
}

Каковы некоторые варианты, чтобы избежать дублирования этого блока try/catch везде? Важно, что если FakeApi выдает исключение или возвращает null, необходимо вызвать RealApi.


person Schrute    schedule 15.09.2020    source источник
comment
Оболочка, которая инкапсулирует это поведение, и вы увидите только Wrapper.m1(s); вызовов в своем коде?   -  person Kayaman    schedule 15.09.2020


Ответы (2)


Один из вариантов — инкапсулировать поведение проверки ошибок в отдельный метод:

public <T> T fakeOrReal(Supplier<T> fake, Supplier<T> real) {
  try {
    T r = fake.get();
    if (r != null) {
      return r;
    }
  }
  catch (Exception e) {
    // log error
  }

  return real.get();
}

Затем вы можете просто вызвать его с помощью

public Type1 m1(String s) {
  return fakeOrReal(() -> FakeApi.m1(s), () -> RealApi.m1(s));
}
person Thomas Preißler    schedule 15.09.2020
comment
С этой опцией все равно придется повторять последний блок кода для каждого метода, который они хотят использовать. - person Olivier Grégoire; 15.09.2020

Это не так просто, как ответ Томаса Прейслера, но поможет вам вообще не повторять какой-либо метод. Поэтому, если вы расширяете интерфейс, вам нужно изменить только конкретные классы, а не компоновщик, который описывает фактическое поведение, которое вы хотите.

Создайте интерфейс, содержащий все методы RealApi:

interface Api {
  Type1 m1(String s);
}

Затем класс, который выполняет фактический вызов:

class ConcreteApi implements Api {
  public Type1 m1(String s) {
    return RealApi.m1(s);
  }
}

Затем создайте свой FakeApi:

class TotallyFakeApi implements Api {
  public Type1 m1(String s) {
    return FakeApi.m1(s);
  }
}

Теперь самое сложное, чтобы не повторяться:

private static Object callImplementation(Api api, Method method, Object[] methodArgs) throws Exception {
  Method actualMethod = api.getClass().getMethod(actualMethod.getName(), actualMethod.getParameterTypes());
  return actualMethod.invoke(api, methodArgs);
}
Api fakeOrReal(Api fakeApi, Api realApi) {
  return (Api) Proxy.newProxyInstance(
      FakeApi.class.getClassLoader(),
      new Class[]{Api.class},
      (proxy, method, methodArgs) -> {
        try {
          Object r = callImplementation(fakeApi, method, methodArgs);
          if (r != null) {
            return r;
          }
        } catch (Exception e) {
          // logError(e);
        }
        return callImplementation(realApi, method, methodArgs);
      }
    );
  
}

Получите фактическую реализацию следующим образом:

Api apiToUse = fakeOrReal(new TotallyFakeApi(), new ConcreteApi());
person Olivier Grégoire    schedule 15.09.2020