책임 연쇄 패턴은 핸들러 중 하나가 요청을 처리할 때까지 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다.
이 패턴은 발신자 클래스를 수신자들의 구상 클래스들에 연결하지 않고도 여러 객체가 요청을 처리할 수 있도록 합니다. 체인은 표준 핸들러 인터페이스를 따르는 모든 핸들러와 런타임 때 동적으로 구성될 수 있습니다.
복잡도:
인기도:
사용 예시들: 책임 연쇄 패턴은 자바에서 매우 일반적으로 사용됩니다.
이벤트들을 그래픽 사용자 인터페이스 클래스들 내의 부모 클래스들에 버블링하는 것입니다. 또 다른 주목할만한 사용 사례는 순차적 접근 필터들입니다.
다음은 코어 자바 라이브러리로부터 가져온 몇 가지 예시들입니다:
식별: 패턴의 모든 객체는 공통 인터페이스를 따르며, 다른 객체들의 같은 메서드들을 간접적으로 호출하는 한 객체 그룹의 행동 메서드들이 있습니다.
접근 필터링
이 예시는 사용자 데이터가 포함된 요청이 인증, 권한 부여 및 검정과 같은 다양한 작업을 수행하는 핸들러의 순차적 체인을 통과하는 방법을 보여줍니다.
여러 작가가 제공한 패턴의 표준 버전과 약간 다른 패턴 예시를 제공했습니다. 대부분 패턴 예시들은 올바른 핸들러를 찾아 실행한 후 체인을 종료한다는 개념을 기반으로 생성되었습니다. 그러나 제 예시는 요청을 처리할 수 없는 핸들러가 있을 때까지 모든 핸들러를 실행합니다. 흐름이 약간 다르지만 제 예시는 여전히 책임 연쇄 패턴이라는 점을 유의하세요.
middleware middleware/Middleware.java: 기본 검정 인터페이스
package refactoring_guru.chain_of_responsibility.example.middleware;
/**
* Base middleware class.
*/
public abstract class Middleware {
private Middleware next;
/**
* Builds chains of middleware objects.
*/
public static Middleware link(Middleware first, Middleware... chain) {
Middleware head = first;
for (Middleware nextInChain: chain) {
head.next = nextInChain;
head = nextInChain;
}
return first;
}
/**
* Subclasses will implement this method with concrete checks.
*/
public abstract boolean check(String email, String password);
/**
* Runs check on the next object in chain or ends traversing if we're in
* last object in chain.
*/
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;
/**
* ConcreteHandler. Checks whether there are too many failed login requests.
*/
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();
}
/**
* Please, not that checkNext() call can be inserted both in the beginning
* of this method and in the end.
*
* This gives much more flexibility than a simple loop over all middleware
* objects. For instance, an element of a chain can change the order of
* checks by running its check after all other checks.
*/
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;
/**
* ConcreteHandler. Checks whether a user with the given credentials exists.
*/
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;
/**
* ConcreteHandler. Checks a user's role.
*/
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;
/**
* Server class.
*/
public class Server {
private Map<String, String> users = new HashMap<>();
private Middleware middleware;
/**
* Client passes a chain of object to server. This improves flexibility and
* makes testing the server class easier.
*/
public void setMiddleware(Middleware middleware) {
this.middleware = middleware;
}
/**
* Server gets email and password from client and sends the authorization
* request to the chain.
*/
public boolean logIn(String email, String password) {
if (middleware.check(email, password)) {
System.out.println("Authorization have been successful!");
// Do something useful here for authorized users.
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;
/**
* Demo class. Everything comes together here.
*/
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");
// All checks are linked. Client can build various chains using the same
// components.
Middleware middleware = Middleware.link(
new ThrottlingMiddleware(2),
new UserExistsMiddleware(server),
new RoleCheckMiddleware()
);
// Server gets a chain from client code.
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!