책임 연쇄(Chain of Responsibility) 패턴
요청을 보내는 쪽(sender)과 요청을 처리하는 쪽(receiver)을 분리하는 패턴
핸들러 체인을 사용해서 요청을 처리한다.
책임 연쇄(Chain of Responsibility) 패턴 before
Request
@AllArgsConstructor
@Getter
public class Request {
private String body;
}
단순하게 요청 본문 body를 가지는 클래스
RequestHandler
public class RequestHandler {
public void handle(Request request){
System.out.println(request.getBody());
}
}
요청을 처리하는 클래스
단순하게 요청 본문을 꺼내서 출력한다
Client
public class Client {
public static void main(String[] args) {
Request request = new Request("요청");
RequestHandler requestHandler = new RequestHandler();
requestHandler.handle(request);
}
}
만약 여기서 요청을 처리(출력)하기 전에 해당 요청이 인증된 요청인지 확인하는 작업이 필요하다면?
RequestHandler
public class RequestHandler {
public void handle(Request request){
System.out.println("인증 확인 프로세스");
System.out.println(request.getBody());
}
}
RequestHandler에 인증 확인 프로세스(로직)이 추가된다.
하지만 이런 방식은 SRP를 지키지 못한다.
요청을 처리하는 로직 & 인증을 확인하는 로직 2가지의 책임을 가지게 된다.
public class RequestHandler {
public void handle(Request request){
System.out.println(request.getBody());
}
}
public class AuthRequestHandler extends RequestHandler{
@Override
public void handle(Request request) {
System.out.println("인증 확인 프로세스");
super.handle(request);
}
}
public class Client {
public static void main(String[] args) {
Request request = new Request("요청");
RequestHandler requestHandler = new AuthRequestHandler();
requestHandler.handle(request);
}
}
// 출력
인증 확인 프로세스
요청
AuthRequestHandler는 RequestHandler를 구현한 클래스이다.
RequestHandler의 handle을 호출하기 전에 인증 확인 프로세스를 진행한다.
이 구조는 상속을 통한 프록시 패턴과 동일하다.
이렇게 함으로써 SRP를 지키게 되었다.
클라이언트는 AuthRequestHandler를 통해 handle을 요청해야
절차(인증 - 처리) 과정을 할 수 있다.
만약 로깅이 필요하다면?
public class LoggingRequestHandler extends RequestHandler{
@Override
public void handle(Request request) {
System.out.println("로깅");
super.handle(request);
}
}
AuthRequestHandler와 마찬가지로 클라이언트는 LoggingRequestHandler를 통해서 handle() 요청을 해야지
로깅 - 처리 과정을 할 수 있다.
하지만 요청 처리에 대한 프로세스가 인증 - 로깅 - 처리 / 로깅 - 인증 - 처리
이렇게 프로세스가 바뀔 수도 있다.
그럼 현재 방식은 클라이언트가 handle 요청을 LoggingRequestHandler or AuthRequestHandler
어떤 프로세스를 먼저 하는지에 따라서 클라이언트가 사용하는 Handler가 바뀌고 상속 순서도 달라진다.
인증 - 로깅 - 처리
LoggingRequestHandelr extends RequestHandler
AuthRequestHandler extends LoggingRequestHandler
로깅 - 인증 - 처리
AuthRequestHandler extends RequestHandler
LoggingRequestHandelr extends AuthRequestHandler
이런 문제점을 책임 연쇄 패턴을 적용해서 해결하자
책임 연쇄(Chain of Responsibility) 패턴 after
adstract class RequestHandler
public abstract class RequestHandler {
private RequestHandler nextHandler;
public RequestHandler(RequestHandler nextHandler) {
this.nextHandler = nextHandler;
}
public void handle(Request request){
if(nextHandler != null){
nextHandler.handle(request);
}
}
}
RequestHandler는 추상 클래스이다.
필드로 다음 handler를 가진다.
요청을 처리하는 여러가지 클래스
public class PrintRequestHandler extends RequestHandler{
public PrintRequestHandler(RequestHandler nextHandler) {
super(nextHandler);
}
@Override
public void handle(Request request) {
System.out.println(request.getBody());
super.handle(request);
}
}
public class LoggingRequestHandler extends RequestHandler{
public LoggingRequestHandler(RequestHandler nextHandler) {
super(nextHandler);
}
@Override
public void handle(Request request) {
System.out.println("로깅");
super.handle(request);
}
}
public class AuthRequestHandler extends RequestHandler{
public AuthRequestHandler(RequestHandler nextHandler) {
super(nextHandler);
}
@Override
public void handle(Request request) {
System.out.println("인증 프로세스");
super.handle(request);
}
}
클라이언트
public class Client {
private RequestHandler requestHandler;
public Client(RequestHandler requestHandler) {
this.requestHandler = requestHandler;
}
public void doWork(){
Request request = new Request("요청");
requestHandler.handle(request);
}
public static void main(String[] args) {
RequestHandler chain =
new AuthRequestHandler(new LoggingRequestHandler(new PrintRequestHandler(null)));
Client client = new Client(chain);
client.doWork();
}
}
클라이언트는 이제 구체적인 RequestHandler에 의존하는 게 아닌
추상 클래스인 RequestHandler에만 의존한다.
책임 연쇄 패턴을 적용함으로써 구체적인 RequestHandler에 대한 낮은 결합도가 적용되었다.
클라언트는 외부에서 만들어주는 체인(RequestHandler)을 받아서 사용만 한다.
이로 인해서 클라이언트 코드의 변경 없이 새로운 핸들러 추가나 핸들러의 체인 순서가 중요한 경우 순서를 바꿔서
체인을 클라이언트가 사용할 수 있게 되었다.
클라이언트에 핸들러들을 감싸는 형태로 생성하여 주입하는데 이는 마치 데코레이터 패턴 같다.
각 패턴들의 구조의 모습은 비슷하나 어떤 문제(목적)을 해결하려고 하는지 이해하는 게 중요하다.
데코레이터 패턴은 기능 확장 책임 연쇄 패턴은 동적인 처리 구성과 책임 분산의 목적을 가진다.
빌더 패턴 + 책임 연쇄 패턴 custom
public class RequestHandlerBuilder {
private List<RequestHandler> requestHandlers = new ArrayList<>();
private RequestHandlerBuilder(){
}
public static RequestHandlerBuilder builder(){
return new RequestHandlerBuilder();
}
public RequestHandlerBuilder addHandler(RequestHandler requestHandler){
requestHandlers.add(requestHandler);
return this;
}
public RequestHandler build(){
RequestHandler start = null;
RequestHandler before = null;
int size = requestHandlers.size();
for(int i = size - 1; i >= 0; i--){
RequestHandler cur = requestHandlers.get(i);
cur.setNextHandler(before);
before = cur;
if(i == 0) start = cur;
}
return start;
}
}
핸들러들을 감싸서 체인을 만들어주는게 직관적이지 않다고 생각해서
빌더 패턴을 통해서 빌더를 만들었다.
빌더 사용 클라이언트 코드
public static void main(String[] args) {
Client client = new Client(RequestHandlerBuilder.builder()
.addHandler(new AuthRequestHandler())
.addHandler(new LoggingRequestHandler())
.addHandler(new PrintRequestHandler()).build());
client.doWork();
}
책임 연쇄(Chain of Responsibility) 패턴 정리
장점
클라이언트 코드를 변경하지 않고 새로운 핸들러를 체인에 추가할 수 있다
각각의 체인은 자신이 해야하는 일만 한다.
체인을 다양한 방법으로 구성할 수 있다
단점
디버깅이 조금 어렵다.
책임 연쇄(Chain of Responsibility) 패턴 적용 사례
참고자료
'Computer Sience > Desgin Pattern' 카테고리의 다른 글
[Design Pattern] 인터프리터(Interpreter) 패턴 (0) | 2024.10.31 |
---|---|
[Design Pattern] 커맨드(Command) 패턴 (1) | 2024.10.31 |
[Design Pattern] 프록시(Proxy) 패턴 + 다이나믹 프록시 (0) | 2024.10.30 |
[Design Pattern] 플라이웨이트(Flyweight) 패턴 (1) | 2024.10.30 |
[Design Pattern] 퍼사드(Facade) 패턴 (1) | 2024.10.28 |