어댑터 패턴

기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 박꿔주는 패턴

image
  • Client는 Target Interface를 사용

  • Adaptee를 사용하기 위해 TargetAdaptee를 연결할 Adapter사용

어댑터 패턴의 핵심은 adapteeadapter를 통해 target으로 변환시켜주는 것이다.

예제1

image
  • CType: Target

  • Adapter: Adapter

  • EightPin: Adaptee

위의 코드를 보면 client(CType port) 는 CType을 인자로 받는다. 여기서 만약 8핀으로 수정하고 싶으면 client()를 수정해야한다.

👉 그럼 왜 `Adapter`를 사용할까?

현재 EightPin(Adaptee)는 직접 작성한 클래스라 수정이 가능하지만 만약 라이브러리로 제공되는 클래스라면 수정이 불가능할 것이다. 그렇다면 CType클래스의 코드를 변경할 수도 있지만 이 경우에도 기존에 CType을 많은 곳에서 사용할 수록 수정 작업이 쉽지 않을뿐 아니라 오류 발생율도 증가하게 될 것이다.

예제2

다음 예제는 Spring Security에서 제공하는 UserDetails에 관한 코드이다.

  • UserDetails

    • username과 password 정보를 알아낼 수 있는 인터페이스

    • Target에 해당

  • UserDetailsService

    • username에 해당하는 UserDetails 유저 정보를 읽어들이는 인터페이스

    • Target에 해당

  • LoginHandler

    • UserDetails와 UserDetailsService로 로그인을 처리하는 핸들러

    • Client에 해당

Account

  • 애플리케이션마다 (각 애플리케이션에 맞게) 만드는 일반적인 Account

  • security 패키지에서 제공하는 클래스와 다르게 해당 애플리케이션에서만 사용하는 용도의 클래스이다.

  • Adaptee에 해당

AccountService

  • 애플리케이션마다 (각 애플리케이션에 맞게) 만드는 일반적인 AccountService

  • security 패키지에서 제공하는 클래스와 다르게 해당 애플리케이션에서만 사용하는 용도의 클래스이다.

  • Adaptee에 해당

Client 코드에 해당하는 로그인 기능을 처리해주는 LoginHandlerUserDatilsUserDetailsService라는 정해진 규격의 인터페이스를 사용하고 있다. (Target에 해당)

우리 애플리케이션의 AccountAccountServiceAdaptee에 해당한다.

여기에 중간 어댑터를 만들어서 현재 security 내의 클래스와 상호호환되지 않는 이 두 클래스를 호환시킨 코드는 다음과 같이 만들 수 있다.

AccountUserDetailsService

Adaptee를 사용해서 Target 인터페이스 규약에 맞도록 구현해준다.

  1. UserDetailsService 인터페이스를 implements

  2. Adaptee에 해당하는 AccountService를 필드로 가지고 사용

  3. loadUser()를 Override할 때, AccountService를 사용

  4. 이 때, AccountServiceUserDetails와 상관없는 Account를 넘겨주기 때문에 이를 다시 UserDetails로 변환해주는 어댑터가 필요

AccountUserDetails

  1. UserDetails라는 Target을 Adaptee에 해당하는 Account를 사용해서 구현

예제1 에서와 같이 예제2 코드를 보면 기존의

UserDetailServiceAccountUserDetailService(Adapter)를 통해 AccountService(Adaptee)로 대체됐다.

또한 AccountUserDetails(UserDetailsAdapter)를 통해 UserDetails대신 Account(adaptee)로 대체된 것을 확인할 수 있다.


장/단점

장점

  • 기존 코드(Adaptee)를 변경하지 않고 원하는 인터페이스(Target) 구현체를 만들어 재사용할 수 있다.

    • 기존 코드를 변경하지 않고, 확장할 수 있다는 점에서 OCP(Open Closed Principle) 원칙에 가까운 패턴이다.

  • 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

    • 각각 하던 일에 집중할 수 있기 때문에 SRP(Single Responsibility Principle) 원칙에 가까운 패턴이다

단점

  • 클래스가 많아지고, 구조가 복잡해진다.


실무 예제

java.util.Arrays#asList(T...)

  • 배열을 리스트로 변환해준다.

  • 배열(Target) → (Adapter)→ 리스트(Adaptee)

  • T... : 가변인자 - 내부적으로는 배열로 넘겨받게 된다.

HandlerAdapter

  • 핸들러 : 요청을 처리하고 응답을 반환

우리가 작성하는 다양한 형태의 핸들러 코드를 스프링 MVC가 실행할 수 있는 형태로 변환해주는 어댑터용 인터페이스.

  • 가장 많이 사용하는 형태의 핸들러

  • doDispatch() 코드 일부

  • getHandlerAdapter()

핸들러는 다양한 형태이기 때문에 Object 타입으로 받아온다.

핸들러를 처리할 수 있는 HandlerAdapter를 찾아서 반환한다.

어떤 핸들러를 사용하느냐에 따라 각기 다른 핸들러 어댑터를 사용하게 된다.

핸들러 어댑터는 간단한 인터페이스만 구현해주면 된다.

핸들러 어댑터는 요청을 처리하는 방법을 구현해주면 된다.

그 중 가장 많이 사용하는 것이 RequestMappingHandlerAdapter이다.

원한다면 직접만들어서 구현할 수 있다.

스프링은 Adapter에 해당하는 인터페이스를 제공해주는 것이다.

  • HttpServletRequestHttpServletResponse를 받아서 ModelAndView를 반환해주는 어댑터에 대한 인터페이스를 정의한 것이 HandlerAdapter이다.

왜 이런 어댑터 인터페이스가 필요했을까?

다양한 형태의 핸들러가 있고, 각기 다른 형태에 따라 각각 다르게 처리해야하기 때문에, 다르게 처리해야하는 모든 핸들러가 스프링MVC에 들어있고, 다양한 형태의 핸들러를 다 지원할 수 있게(확장에 열려있게) 해주기 위해 스프링 MVC가 고안해놓은 인터페이스이다.

어댑터 패턴을 이해한 후에 SpringMVC의 DispatcherServlet 구현 코드를 읽어보고 doDispatch의 동작 원리를 공부해보면 좋을 것 같다!

레거시 시스템을 원하는 인터페이스로 사용 가능하게 할 수 있고, 어댑터 객체에서 적절히 구현 후 적용한다면 단순한 wrapping 이상의 효과를 볼 수 있을 것 같다.

추가로 설명하자면

선언한 컨트롤러 클래스내의 메서드를 실행하여 응답을 하는데 이때 반환 값이 String/ModelAndView 로 다양한 결과값을 반환할 수 있습니다. 하지만 Servlet에서는 무조건 ModelAndView를 받아서 이를 사용하도록 구현되었기 때문에 두 사이를 연결하기 위해 HandlerAdpater가 필요합니다.

대표적인 HandlerAdpaterRequestMappingHandlerAdpater / HttpRequestHadlerAdapter / SimpleControllerAdapter가 존재고 이외에도 HandlerFucntionAdapter, SimpleServletHandlerAdpater가 존재합니다.

1. RequestMappingHandlerAdpater

첫번째로 **RequestMappingHandlerAdpater**는 AbstractHandlerMethodAdpater를 상속한 구현체로 @Annotation방식의 핸들러에 사용이 됩니다.

invokeHandlerMethod()를 통해 컨트롤러의 메서드를 실행하고 getModelAndView()메서드를 통해 ModelAndView로 바꿔 반환합니다.

추가적으로 핸들러에 대해 설명하자면 아래에 설명할 다른 핸들러는 하나의 컨트롤러가 하나의 url에 만 맵핑되지만, 이 방식은 하나의 컨트롤러에서 여러 url과 맵핑될 수 있습니다.

2. HttpRequestHandlerAdapter

두번째로 HttpRequestHandlerAdapterHttpRequestHandler라는 인터페이스를 구현한 컨트롤러(핸들러)를 위한 AdapterModel/view를 사용하지 않는 Http기반의 low level서비스를 개발할때 사용할 수 있습니다.

별도의 Model/View를 사용하지 않기 때문에 null을 반환하는 것을 볼 수 있습니다.

3. SimpleControllerHandlerAdpter

Controller라는 인터페이스를 구현한 컨트롤러(핸들러)를 위한 Adapter로 어노테이션방식의 컨트롤러가 등장하기 전의 가장 기본적인 컨트롤러 타입입니다.

Controller 인터페이스의 handleRequest()ModelAndView를 반환하기에 그대로 실행하여 반환하는 것을 볼 수 있습니다.

Last updated