Цепочка обязанностей на 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!