
Цепочка обязанностей на Java
Цепочка обязанностей — это поведенческий паттерн, позволяющий передавать запрос по цепочке потенциальных обработчиков, пока один из них не обработает запрос.
Избавляет от жёсткой привязки отправителя запроса к его получателю, позволяя выстраивать цепь из различных обработчиков динамически.
Сложность:
Популярность:
Применимость: Паттерн встречается в Java не так уж часто, так как для его применения нужна цепь объектов, например, связанный список.
Область применения цепочки обязанностей — всевозможные обработчики событий, последовательные проверки доступа и прочее.
Примеры Цепочки обязанностей в стандартных библиотеках Java:
Признаки применения паттерна: Цепочку обязанностей можно определить по спискам обработчиков или проверок, через которые пропускаются запросы. Особенно если порядок следования обработчиков важен.
Слои авторизации и аутентификации пользователей
Этот пример показывает как пользовательские данные проходят последовательную аутентификацию в множестве обработчиков, связанных в одну цепь.
Этот пример отличается от канонической версии тем, что проверка обрывается, если очередной обработчик не может обработать запрос. В классическом варианте, следование по цепочке заканчивается как только нашёлся элемент цепи, который может обработать запрос. Просто знайте, что Концептуальный пример от этого не меняется, а код отличается только условием выхода из цепи.
middleware
middleware/Middleware.java: Базовый класс проверок
package refactoring_guru.chain_of_responsibility.example.middleware; /** * Базовый класс цепочки. */ public abstract class Middleware { private Middleware next; /** * Помогает строить цепь из объектов-проверок. */ public static Middleware link(Middleware first, Middleware... chain) { Middleware head = first; for (Middleware nextInChain: chain) { head.next = nextInChain; head = nextInChain; } return first; } /** * Подклассы реализуют в этом методе конкретные проверки. */ public abstract boolean check(String email, String password); /** * Запускает проверку в следующем объекте или завершает проверку, если мы в * последнем элементе цепи. */ protected boolean checkNext(String email, String password) { if (next == null) { return true; } return next.check(email, password); } }
middleware/ThrottlingMiddleware.java: Проверка на лимит запросов
package refactoring_guru.chain_of_responsibility.example.middleware; /** * Конкретный элемент цепи обрабатывает запрос по-своему. */ public class ThrottlingMiddleware extends Middleware { private int requestPerMinute; private int request; private long currentTime; public ThrottlingMiddleware(int requestPerMinute) { this.requestPerMinute = requestPerMinute; this.currentTime = System.currentTimeMillis(); } /** * Обратите внимание, вызов checkNext() можно вставить как в начале этого * метода, так и в середине или в конце. * * Это даёт еще один уровень гибкости по сравнению с проверками в цикле. * Например, элемент цепи может пропустить все остальные проверки вперёд и * запустить свою проверку в конце. */ public boolean check(String email, String password) { if (System.currentTimeMillis() > currentTime + 60_000) { request = 0; currentTime = System.currentTimeMillis(); } request++; if (request > requestPerMinute) { System.out.println("Request limit exceeded!"); Thread.currentThread().stop(); } return checkNext(email, password); } }
middleware/UserExistsMiddleware.java: Проверка пароля
package refactoring_guru.chain_of_responsibility.example.middleware; import refactoring_guru.chain_of_responsibility.example.server.Server; /** * Конкретный элемент цепи обрабатывает запрос по-своему. */ public class UserExistsMiddleware extends Middleware { private Server server; public UserExistsMiddleware(Server server) { this.server = server; } public boolean check(String email, String password) { if (!server.hasEmail(email)) { System.out.println("This email is not registered!"); return false; } if (!server.isValidPassword(email, password)) { System.out.println("Wrong password!"); return false; } return checkNext(email, password); } }
middleware/RoleCheckMiddleware.java: Проверка роли
package refactoring_guru.chain_of_responsibility.example.middleware; /** * Конкретный элемент цепи обрабатывает запрос по-своему. */ public class RoleCheckMiddleware extends Middleware { public boolean check(String email, String password) { if (email.equals("admin@example.com")) { System.out.println("Hello, admin!"); return true; } System.out.println("Hello, user!"); return checkNext(email, password); } }
server
server/Server.java: Сервер, на который заходим
package refactoring_guru.chain_of_responsibility.example.server; import refactoring_guru.chain_of_responsibility.example.middleware.Middleware; import java.util.HashMap; import java.util.Map; /** * Класс сервера. */ public class Server { private Map<String, String> users = new HashMap<>(); private Middleware middleware; /** * Клиент подаёт готовую цепочку в сервер. Это увеличивает гибкость и * упрощает тестирование класса сервера. */ public void setMiddleware(Middleware middleware) { this.middleware = middleware; } /** * Сервер получает email и пароль от клиента и запускает проверку * авторизации у цепочки. */ public boolean logIn(String email, String password) { if (middleware.check(email, password)) { System.out.println("Authorization have been successful!"); // Здесь должен быть какой-то полезный код, работающий для // авторизированных пользователей. return true; } return false; } public void register(String email, String password) { users.put(email, password); } public boolean hasEmail(String email) { return users.containsKey(email); } public boolean isValidPassword(String email, String password) { return users.get(email).equals(password); } }
Demo.java: Клиентский код
package refactoring_guru.chain_of_responsibility.example; import refactoring_guru.chain_of_responsibility.example.middleware.Middleware; import refactoring_guru.chain_of_responsibility.example.middleware.RoleCheckMiddleware; import refactoring_guru.chain_of_responsibility.example.middleware.ThrottlingMiddleware; import refactoring_guru.chain_of_responsibility.example.middleware.UserExistsMiddleware; import refactoring_guru.chain_of_responsibility.example.server.Server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Демо-класс. Здесь всё сводится воедино. */ public class Demo { private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private static Server server; private static void init() { server = new Server(); server.register("admin@example.com", "admin_pass"); server.register("user@example.com", "user_pass"); // Проверки связаны в одну цепь. Клиент может строить различные цепи, // используя одни и те же компоненты. Middleware middleware = Middleware.link( new ThrottlingMiddleware(2), new UserExistsMiddleware(server), new RoleCheckMiddleware() ); // Сервер получает цепочку от клиентского кода. server.setMiddleware(middleware); } public static void main(String[] args) throws IOException { init(); boolean success; do { System.out.print("Enter email: "); String email = reader.readLine(); System.out.print("Input password: "); String password = reader.readLine(); success = server.logIn(email, password); } while (!success); } }
OutputDemo.txt: Результат выполнения
Enter email: admin@example.com Input password: admin_pass Hello, admin! Authorization have been successful! Enter email: wrong@example.com Input password: wrong_pass This email is not registered! Enter email: wrong@example.com Input password: wrong_pass This email is not registered! Enter email: wrong@example.com Input password: wrong_pass Request limit exceeded!