책임 연쇄 패턴 (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 |