Шаблон проектирования цепочки ответственности в Java

Шаблон проектирования цепочки ответственности в Java

1. Вступление

В этой статье мы рассмотрим широко используемыйbehavioral design pattern:Chain of Responsibility.

Мы можем найти больше шаблонов проектирования в нашихprevious article.

2. Цепочка ответственности

Wikipedia определяет Цепочку ответственности как шаблон проектирования, состоящий из «источника командных объектов и серии объектов обработки».

Каждый объект обработки в цепочке отвечает за определенный тип команды, и обработка завершается, он передает команду следующему процессору в цепочке.

Шаблон Chain of Responsibility удобен для:

  • Разделение отправителя и получателя команды

  • Выбор стратегии обработки во время обработки

Итак, давайте посмотрим на простой пример шаблона.

3. пример

Мы собираемся использовать цепочку ответственности для создания цепочки для обработки запросов аутентификации.

Таким образом, провайдер аутентификации на входе будетcommand, а каждый процессор аутентификации будет отдельным объектомprocessor.

Давайте сначала создадим абстрактный базовый класс для наших процессоров:

public abstract class AuthenticationProcessor {

    public AuthenticationProcessor nextProcessor;

    // standard constructors

    public abstract boolean isAuthorized(AuthenticationProvider authProvider);
}

Затем давайте создадим конкретные процессоры, которые расширяютAuthenticationProcessor:

public class OAuthProcessor extends AuthenticationProcessor {

    public OAuthProcessor(AuthenticationProcessor nextProcessor) {
        super(nextProcessor);
    }

    @Override
    public boolean isAuthorized(AuthenticationProvider authProvider) {
        if (authProvider instanceof OAuthTokenProvider) {
            return true;
        } else if (nextProcessor != null) {
            return nextProcessor.isAuthorized(authProvider);
        }

        return false;
    }
}
public class UsernamePasswordProcessor extends AuthenticationProcessor {

    public UsernamePasswordProcessor(AuthenticationProcessor nextProcessor) {
        super(nextProcessor);
    }

    @Override
    public boolean isAuthorized(AuthenticationProvider authProvider) {
        if (authProvider instanceof UsernamePasswordProvider) {
            return true;
        } else if (nextProcessor != null) {
            return nextProcessor.isAuthorized(authProvider);
        }
        return false;
    }
}

Здесь мы создали два конкретных процессора для наших входящих запросов авторизации:UsernamePasswordProcessor иOAuthProcessor..

Для каждого из них мы отказались от методаisAuthorized.

А теперь давайте создадим пару тестов:

public class ChainOfResponsibilityTest {

    private static AuthenticationProcessor getChainOfAuthProcessor() {
        AuthenticationProcessor oAuthProcessor = new OAuthProcessor(null);
        return new UsernamePasswordProcessor(oAuthProcessor);
    }

    @Test
    public void givenOAuthProvider_whenCheckingAuthorized_thenSuccess() {
        AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();
        assertTrue(authProcessorChain.isAuthorized(new OAuthTokenProvider()));
    }

    @Test
    public void givenSamlProvider_whenCheckingAuthorized_thenSuccess() {
        AuthenticationProcessor authProcessorChain = getChainOfAuthProcessor();

        assertFalse(authProcessorChain.isAuthorized(new SamlTokenProvider()));
    }
}

В приведенном выше примере создается цепочка процессоров аутентификации:UsernamePasswordProcessor → OAuthProcessor. В первом тесте авторизация проходит успешно, а в другом - неудачно.

СначалаUsernamePasswordProcessor проверяет, является ли провайдер аутентификации экземпляромUsernamePasswordProvider.

Не являясь ожидаемым вводом,UsernamePasswordProcessor делегируетOAuthProcessor.

Наконец,OAuthProcessor обрабатывает команду. В первом тесте есть совпадение и тест проходит. Во втором случае в цепочке больше нет процессоров, в результате чего тест не пройден.

4. Принципы реализации

Мы должны помнить о нескольких важных принципах при реализации Цепочки ответственности:

  • У каждого процессора в цепочке будет своя реализация для обработки команды.

    • В нашем примере выше все процессоры имеют свою реализациюisAuthorized

  • Каждый процессор в цепочке должен иметь ссылку на следующий процессор.

    • ВышеUsernamePasswordProcessor делегируетOAuthProcessor

  • Каждый процессор отвечает за делегирование следующему процессору, поэтому остерегайтесь сброшенных команд.

    • Опять же в нашем примере, если команда является экземпляромSamlProvider, тогда запрос может не быть обработан и будет неавторизованным.

  • Процессоры не должны образовывать рекурсивный цикл

    • В нашем примере у нас нет цикла в нашей цепочке:UsernamePasswordProcessor → OAuthProcessor. Но, если мы явно установимUsernamePasswordProcessor в качестве следующего процессораOAuthProcessor,then we end up with a cycle in our chain:UsernamePasswordProcessor → OAuthProcessor → UsernamePasswordProcessor. В этом может помочь следующий процессор в конструкторе.

  • Только один процессор в цепочке обрабатывает данную команду

    • В нашем примере, если входящая команда содержит экземплярOAuthTokenProvider, то толькоOAuthProcessor будет обрабатывать команду

5. Использование в реальном мире

В мире Java мы ежедневно пользуемся цепочкой ответственности. One such classic example is Servlet Filters inJava, которые позволяют нескольким фильтрам обрабатывать HTTP-запрос. Хотя в этом случаеeach filter invokes the chain instead of the next filter.

Давайте посмотрим на приведенный ниже фрагмент кода, чтобы лучше понять этот шаблон в Servlet Filters:.

public class CustomFilter implements Filter {

    public void doFilter(
      ServletRequest request,
      ServletResponse response,
      FilterChain chain)
      throws IOException, ServletException {

        // process the request

        // pass the request (i.e. the command) along the filter chain
        chain.doFilter(request, response);
    }
}

Как видно из фрагмента кода выше, нам нужно вызвать методFilterChain‘sdoFilter, чтобы передать запрос следующему процессору в цепочке.

6. Недостатки

И теперь, когда мы увидели, насколько интересна цепочка ответственности, давайте не будем забывать о некоторых недостатках:

  • В основном, это может легко сломаться:

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

    • если процессор вызывает неправильный процессор, это может привести к циклу

  • Он может создавать глубокие трассировки стека, которые могут повлиять на производительность

  • Это может привести к дублированию кода между процессорами, увеличивая объем обслуживания

7. Заключение

В этой статье мы говорили о цепочке ответственности, ее сильных и слабых сторонах с помощью цепочки для авторизации входящих запросов аутентификации.

И, как всегда, исходный код можно найтиover on GitHub.