[CleanCode] 어댑터 패턴

1 분 소요

들어가며

외부 라이브러리 활용시 사용하는 어댑터 패턴을 알아보기 위한 포스팅

어댑터 패턴

  • 외부 라이브러리를 사용하는 클라이언트 입장에서는 외부 라이브러리를 뜯어보고 사용방법을 익혀야 한다.
  • 만약 라이브러리가 교체되거나 구현체가 바뀐다면 클라이언트는 사용법을 처음부터 다시 공부해야 한다.
  • 어댑터 패턴을 이용하면 위와 같은 문제를 해결할 수 있다. 라이브러리를 이용하는 클라이언트에게는 추상화된 인터페이스를 제공하고 해당 인터페이스를 구현한 어댑터를 통해 외부 라이브러리 호출 로직을 설계할 수 있다.
  • 이를 통해 클라이언트는 제공되는 인터페이스의 메서드만을 보고 손쉽게 외부 라이브러리 사용이 가능해진다.
  • 또한 라이브러리 변경이나 새로운 라이브러리가 추가되는 경우 인터페이스 구현체인 새로운 어댑터를 도입함으로써 클라이언트는 해당 라이브러리를 지원하는 어댑터를 찾아 해당 어댑터를 호출하기만 하면 된다.
  • 즉 확장에는 열려있고 클라이언트 입장에서 변경에는 닫혀있는 좋은 설계가 가능해진다.

대표적인 예

  • Spring MVC 구조에서 dispatcherservlet이 위와 같은 구조로 이루어져 있다. 먼저 코드부터 보자.
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}
  • 먼저 핸들러 매핑 정보에서 request 에 대응되는 핸들러를 가져온다. 핸들러 매핑 정보는 ApplicationContext를 통한 dispatcherservlet 초기화 시점에 설정되고 AnnotationAwareOrderComparator.sort(this.handlerMappings)를 통해 검색 우선순위가 결정된다.
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    ...
}
  • 이후 해당 핸들러를 처리할 수 있는 어댑터를 가져온다. 어댑터 정보와 우선순위는 위와 유사하게 설계되어 있다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 이후 클라이언트라고 할 수 있는 dispatcherservlet는 인터페이스인 HandlerAdapter로 추상화된 handle 메서드만 호출하여 애플리케이션의 결과인 ModelAndView를 결과로 반환받을 수 있게 된다.

-> 위와 같은 설계는 무엇이 장점일까?? 먼저 말 그대로 추상화이다. dispatcherservlet는 어댑터를 찾고 handle만 호출하면 원하는 결과를 얻을 수 있다. 그 다음 장점으로는 확장성이다. 만약 새로운 핸들러가 추가된다면 해당 핸들러를 처리할 수 있는 어댑터만 목록에 추가해 주면 된다. 대박

댓글남기기