Перейти к основному содержимому

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

· 4 мин. чтения

1. Введение

В этой статье мы рассмотрим широко используемый шаблон поведенческого проектирования : цепочка ответственности .

Мы можем найти больше шаблонов проектирования в нашей предыдущей статье .

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

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

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

Шаблон цепочки ответственности удобен для:

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

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

3. Пример

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

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

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

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, то получим цикл в нашей цепочке : UsernamePasswordProcessor -> OAuthProcessor -> UsernamePasswordProcessor. В этом может помочь взятие следующего процессора в конструкторе

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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