Резервный шаблон Java

Я пытаюсь найти хороший способ реализации службы, основанной на классе сторонней библиотеки. У меня также есть реализация «по умолчанию», которую можно использовать в качестве запасного варианта на случай, если библиотека недоступна или не может дать ответ.

public interface Service {

    public Object compute1();

    public Object compute2();
}

public class DefaultService implements Service {

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

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

Фактическая реализация службы будет выглядеть примерно так:

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

Текущая реализация, кажется, немного дублирует вещи в том смысле, что различаются только фактические вызовы служб, но механизм try/catch и механизм по умолчанию почти одинаковы. Кроме того, если бы в службу был добавлен еще один метод, реализация выглядела бы почти одинаково.

Может ли здесь применяться шаблон проектирования (прокси, strategy), чтобы код выглядел лучше и чтобы дальнейшие добавления были менее копипасты?


person user4132657    schedule 26.04.2015    source источник
comment
Какую версию Java вы используете?   -  person Radiodef    schedule 26.04.2015


Ответы (3)


Вы можете выделить общую логику в отдельный метод, используя ссылки на методы, например:

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();
        }
    }
}
person fgb    schedule 26.04.2015
comment
Я сомневаюсь, что это решение может быть хорошо применено, если методы принимают аргументы. Вам придется перегрузить run по крайней мере для Function и BiFunction, а затем вам может понадобиться TriFunction, но он не очень хорошо масштабируется. Кроме того, каждый новый метод Service нуждается в соответствующей реализации в ServiceImpl, что также плохо масштабируется и может быть источником ошибок и проблем с обслуживанием. - person Didier L; 27.04.2015
comment
Согласен с @Didier, хотя это решение для этого конкретного случая, это не очень хорошее общее решение. Дескрипторы методов — это круто, но, вероятно, не очень хорошо здесь подходят. - person Guillaume; 27.04.2015
comment
Если fallback.get() вызывает исключение в блоке try, оно повторяется в блоке catch. - person jaco0646; 19.10.2018

Прокси, вероятно, может помочь вам здесь. Пример ниже не тестировался, но должен дать вам представление о том, что вы могли бы добавить:

public class FallbackService implements InvocationHandler {

    private final Service primaryService;
    private final Service fallbackService;

    private FallbackService(Service primaryService, Service fallbackService) {
        this.primaryService = primaryService;
        this.fallbackService = fallbackService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Object result = method.invoke(primaryService, args);
            if (result != null) return result;
        } catch (Exception ignore) {}
        return method.invoke(fallbackService, args);
    }

    public static Service createFallbackService(Service primaryService, Service fallbackService) {
        return (Service) Proxy.newProxyInstance(
                Service.class.getClassLoader(),
                new Class[] { Service.class },
                new FallbackService(primaryService, fallbackService)
        );
    }
}
person Guillaume    schedule 26.04.2015

Одной из лучших библиотек для этого является Hystrix от Netflix. Я не уверен, нужна ли вам такая тяжелая работа. Это даст вам пул потоков, тайм-ауты, резервные копии, мониторинг, изменения конфигурации во время выполнения, короткое замыкание и т. д.

По сути, это библиотека для защиты вашего кода от сбоев его зависимостей.

person piotrek    schedule 26.04.2015
comment
Хайстрикс отлично выглядит! Я оцениваю это прямо сейчас. Он дает гораздо больше, чем то, о чем просил ОП, но это может быть хорошо... - person Guillaume; 26.04.2015
comment
Спасибо, что упомянули Hystrix. Не знал об этом, но это намного больше, чем мне нужно, и у нас довольно строгая политика в отношении добавления библиотек. Хотя проверю - person user4132657; 02.05.2015