ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 책임 연쇄 패턴 (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

     

Designed by Tistory.