Java fallback pattern

user4132657 picture user4132657 · Apr 26, 2015 · Viewed 7.1k times · Source

I'm trying to find a nice way of implementing a service which relies on a third-party library class. I also have a 'default' implementation to use as fallback in case the library is unavailable or can not provide an answer.

public interface Service {

    public Object compute1();

    public Object compute2();
}

public class DefaultService implements Service {

    @Override
    public Object compute1() {
       // ...
    }

    @Override
    public Object compute2() {
        // ...
    }
}

The actual implementation of the service would be something like:

public class ServiceImpl implements Service {
    Service defaultService = new DefaultService();
    ThirdPartyService thirdPartyService = new ThirdPartyService();

    @Override
    public Object compute1() {
        try {
            Object obj = thirdPartyService.customCompute1();
            return obj != null ? obj : defaultService.compute1();
        } 
        catch (Exception e) {
            return defaultService.compute1();
        }
    }

    @Override
    public Object compute2() {
        try {
            Object obj = thirdPartyService.customCompute2();
            return obj != null ? obj : defaultService.compute2();
        } 
        catch (Exception e) {
            return defaultService.compute2();
        }
    }
}

The current implementation seems to duplicate things a bit in the way that only the actual calls to the services are different, but the try/catch and the default mechanism are pretty much the same. Also, if another method was added in the service, the implementation would look almost alike.

Is there a design pattern that might apply here (proxy, strategy) to make the code look better and make further additions less copy-paste?

Answer

fgb picture fgb · Apr 26, 2015

You can extract the common logic into a separate method using method references, like:

public class ServiceImpl implements Service {
    Service defaultService = new DefaultService();
    ThirdPartyService thirdPartyService = new ThirdPartyService();

    @Override
    public Object compute1() {
        return run(thirdPartyService::customCompute1, defaultService::compute1);
    }

    @Override
    public Object compute2() {
        return run(thirdPartyService::customCompute2, defaultService::compute2);
    }

    private static <T> T run(Supplier<T> action, Supplier<T> fallback) {
        try {
            T result = action.get();
            return result != null ? result : fallback.get();
        } catch(Exception e) {
            return fallback.get();
        }
    }
}