Avoiding code duplication when checking for default responses

Viewed 59

I have a Java program that calls an external API (RealApi in the code below) and sometimes I want to avoid calling this API and instead return pre-constructed responses (generated by FakeApi).

So, I ended up duplicating this kind of construct in most of my methods:

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);
}

What are some options to avoid duplicating this try/catch block everywhere? It's important that if FakeApi throws an exception or returns null, the RealApi must be called.

2 Answers

One option would be encapsulate the error checking behaviour into its own method:

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();
}

You can then just call it with

public Type1 m1(String s) {
  return fakeOrReal(() -> FakeApi.m1(s), () -> RealApi.m1(s));
}

This is not as simple as Thomas Preißler's answer but it will help you not repeat any method at all. So if you expand the interface, you have to modify only the concrete classes and not the linker which describes the actual behavior you want.

Create an interface that contains all the methods of RealApi:

interface Api {
  Type1 m1(String s);
}

Then a class that does the actual call:

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

Then create your FakeApi:

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

Now, the tricky part to avoid repeating yourself:

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);
      }
    );
  
}

Get the actual implementation like this:

Api apiToUse = fakeOrReal(new TotallyFakeApi(), new ConcreteApi());
Related