-
책임 연쇄 패턴 (Chain of Responsibility Pattern)디자인 패턴 2022. 8. 2. 18:27
책임 연쇄 패턴 (Chain of Responsibility Pattern)
요청을 보내는 쪽(sender)과 요청을 처리하는(receiver) 쪽을 분리하는 패턴
- 핸들러 체인을 사용해서 요청을 처리한다.
클라이언트로부터의 요청을 처리할 수 있는 처리 객체를 Chain 으로 만들어 결합을 느슨하게 하기 위해 만들어진 패턴.
일반적으로 요청을 처리할 수 있는 객체를 찾을때 까지 집한 안에서 요청을 전달한다.
책임 연쇄 패턴 적용 전
클라이언트가 특정 핸들러를 사용할 때, 이 클라이언트가 해당 핸들러를 사용할 수 있는 사용자 인지 인증해야 하는 로직이 있다고 가정해보자. 클라이언트 코드는 아래와 같다.
public class Client { public static void main(String[] args) { Request request = new Request("무궁화 꽃이 피었습니다."); RequestHandler requestHandler = new RequestHandler(); requestHandler.handler(request); } }
요청(Request)과 요청 핸들러(Request Handler) 코드는 아래와 같다.
public class Request { String body; public Request(String body) { this.body = body; } public String getBody() { return body; } }
public class RequestHandler { public void handler(Request request) { // 인증 확인 로직 // code 생략.. // 출력 로직 System.out.println(request.getBody()); } }
Request Handler 는 단순히 Request 의 body 를 출력해준다.
문제점
Request Handler 는 현재 하나의 핸들러가 두가지 역할을 진행하고 있다. (단일 책임 원칙 위배)
- 인증 확인 로직
- 출력 로직
이전까지 학습한 패턴을 적용해보자.
책임을 분리하기 위해 인증을 하는 새로운 핸들러를 만들고, Request Handler 를 상속한다.
public class AuthRequestHandler extends RequestHandler{ public void handler(Request request) { // 인증 확인 로직 // code 생략.. super.handler(request); } }
public class RequestHandler { public void handler(Request request) { System.out.println(request.getBody()); } }
AuthRequestHandler 의 handler 메소느는 인증 관련 로직을 처리한 뒤, super 로 원래의 handler 로직을 RequestHandler 에게 맡기면 단일 책임 원칙을 지킬 수 있다.
위 와 같이 각각의 책임을 분리하는것이 좋은 방법 같지만 여전히 문제가 발생한다.
문제점 1.
클라이언트는 구현체를 사용하게 된다. (DIP 의존관계 역전 원칙 위배)
public class Client { public static void main(String[] args) { Request request = new Request("무궁화 꽃이 피었습니다."); RequestHandler requestHandler = new AuthRequestHandler(); requestHandler.handler(request); } }
문제점 2.
logging 과 같은 새로운 로직이 추가 될 경우 이는 어떻게 적용시킬 것인가
public class Client { public static void main(String[] args) { Request request = new Request("무궁화 꽃이 피었습니다."); RequestHandler requestHandler = new AuthRequestHandler(); RequestHandler requestHandler2 = new LoggingHandler(); // ??? requestHandler.handler(request); } }
책임 연쇄 패턴 적용 후
책임 연쇄 패턴을 적용하여 클라이언트는 구체적으로 어떤 핸들러를 사용해야하는지 몰라도 기존의 핸들러 들이 수행되도록 적용해보자.
위 클래스 다이어그램의 Handler 추상타입 클래스를 만들자.
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 의 handle 메소드를 보면 nextHandler 가 있으면 nextHandler 의 handle 을 실행하도록 구현되어 있다.
이제 각 핸들러들은 위 RequestHandler 를 상속받아 작식들의 로직을 구현하면 된다.
AuthRequestHandler
public class AuthRequestHandler extends RequestHandler{ public AuthRequestHandler(RequestHandler nextHandler) { super(nextHandler); } @Override public void handle(Request request) { System.out.println("요청자의 인증을 확인합니다."); if (!request.getBody().split(":")[1].equals("인증OK")) { System.out.println("인증이 완료된 사용자가 아닙니다. 종료합니다."); } else { super.handle(request); } } }
LoggingRequestHandler
public class LoggingRequestHandler extends RequestHandler{ public LoggingRequestHandler(RequestHandler nextHandler) { super(nextHandler); } @Override public void handle(Request request) { System.out.println("요청을 로깅합니다."); super.handle(request); } }
PrintRequestHandler
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); } }
Client
public class Client { private RequestHandler requestHandler; public Client(RequestHandler requestHandler) { this.requestHandler = requestHandler; } // 어떤 핸들러를 사용하는지 구체적으로 알 필요가 없다. public void doWork() { Request request = new Request("요청 부탁해요.:인증OK"); requestHandler.handle(request); } // 외부에서 chain 을 조립한다. public static void main(String[] args) { AuthRequestHandler chain = new AuthRequestHandler( new LoggingRequestHandler(new PrintRequestHandler(null))); Client client = new Client(chain); client.doWork(); } } //결과 요청자의 인증을 확인합니다. 요청을 로깅합니다. 요청 부탁해요.:인증OK
public void doWork() { Request request = new Request("요청 부탁해요.:인증NO"); requestHandler.handle(request); } //결과 요청자의 인증을 확인합니다. 인증이 완료된 사용자가 아닙니다. 종료합니다.
장점과 단점
장점
- 클라이언트 코드를 변경하지 않고, 새로운 핸들러를 체인에 추가할 수 있고, 순서도 변경할 수 있다. (OCP)
- 각 핸들러들은 본인의 역할만을 수행하게 된다. (SRP)
- 체인을 구조적으로 다양하게 구성할 수 있다.
단점
- 다양한 필터들을 거치다 보니 디버깅이 쉽지 않다.
실무에선 어떻게 쓰이나?
- 자바의 서블릿
- 사용자가 요청시 서블릿에 접근하기 전에 다양한 필터를 거치도록 할 수 있다.
- 스프링 시큐리티
- Security 필터에 대한 설정을 할 수 있다.
- ex. http.authorizeRequest().anyRequest().permitAll().and();
참고
코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
'디자인 패턴' 카테고리의 다른 글
인터프리터 패턴 (Interpreter Pattern) (0) 2022.08.03 커맨드 패턴 (Command Pattern) (0) 2022.08.02 프록시 패턴 (Proxy Pattern) (0) 2022.07.22 플라이웨이트 패턴 (Flyweight Pattern) (0) 2022.07.22 퍼사드 패턴 (Facade Pattern) (0) 2022.07.19