위의 코드를 보면 client(CType port) 는 CType을 인자로 받는다. 여기서 만약 8핀으로 수정하고 싶으면 client()를 수정해야한다.
👉 그럼 왜 `Adapter`를 사용할까?
현재 EightPin(Adaptee)는 직접 작성한 클래스라 수정이 가능하지만 만약 라이브러리로 제공되는 클래스라면 수정이 불가능할 것이다. 그렇다면 CType클래스의 코드를 변경할 수도 있지만 이 경우에도 기존에 CType을 많은 곳에서 사용할 수록 수정 작업이 쉽지 않을뿐 아니라 오류 발생율도 증가하게 될 것이다.
2. 예제2
다음 예제는 Spring Security에서 제공하는 UserDetails에 관한 코드이다.
UserDetailService는 AccountUserDetailService(Adapter)를 통해 AccountService(Adaptee)로 대체됐다.
또한 AccountUserDetails(UserDetails 의 Adapter)를 통해 UserDetails대신 Account(adaptee)로 대체된 것을 확인할 수 있다.
3. 장/단점
장점
기존 코드(Adaptee)를 변경하지 않고 원하는 인터페이스(Target) 구현체를 만들어 재사용할 수 있다.
기존 코드를 변경하지 않고, 확장할 수 있다는 점에서 OCP(Open Closed Principle) 원칙에 가까운 패턴이다.
기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.
각각 하던 일에 집중할 수 있기 때문에 SRP(Single Responsibility Principle) 원칙에 가까운 패턴이다
단점
클래스가 많아지고, 구조가 복잡해진다.
4. 실제사용 예시
java.util.Arrays#asList(T...)
List<String> strings =Arrays.asList("a","b","c"); // 배열을 리스트로 변환Enumeration<String> enumeration =Collections.enumeration(strings); // Collections(여기에서는 List)를 Enumeration으로 변환ArrayList<String> list =Collections.list(enumeration); // Enumeration을 list로 변환
배열을 리스트로 변환해준다.
배열(Target) → (Adapter)→ 리스트(Adaptee)
T... : Varargs(가변인자) - 내부적으로는 배열로 넘겨받게 된다.
InputStreamReader / OutputStreamReader
try(InputStream is = new FileInputStream("input.txt"); // 파일로 부터 바이트를 입력 받음
InputStreamReader isr = new InputStreamReader(is); // 바이트를 문자로 변환
BufferedReader reader = new BufferedReader(isr)) { // 문자를 버퍼링
while(reader.ready()) {
System.out.println(reader.readLine());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
InputStream은 바이트 스트림을 입력받고, BufferdReader는 캐릭터 스트림을 입력 받아처리합니다. 이렇게 서로 다른 api를 연결하기 위해 어댑터 역할을 수행하는 것이 InputStreamReader가 됩니다.
HandlerAdapter
핸들러 : 요청을 처리하고 응답을 반환하는 객체로 컨트롤러 클래스가 될수도 있고 메서드가 될 수도 있다.
핸들러 어댑터 : 우리가 작성하는 다양한 형태의 핸들러 코드를 스프링 MVC가 실행할 수 있는 형태로 변환해주는 어댑터용 인터페이스.
// 해당 핸들러를 처리할 수 있는 HandlerAdapter를 찾아온다.
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ..(생략)
// 핸들러를 찾아오면 요청을 처리한다. 처리결과로 model and view를 반환한다.
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
getHandlerAdapter()
핸들러는 다양한 형태이기 때문에 Object 타입으로 받아온다.
핸들러를 처리할 수 있는 HandlerAdapter를 찾아서 반환한다.
protectedHandlerAdaptergetHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters!=null) {for (HandlerAdapter adapter :this.handlerAdapters) {if (adapter.supports(handler)) {return adapter; } } }thrownewServletException("No adapter for handler ["+ handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}
어떤 핸들러를 사용하느냐에 따라 각기 다른 핸들러 어댑터를 사용하게 된다.
HandlerAdapter
publicinterfaceHandlerAdapter { /** * Given a handler instance, return whether or not this {@code HandlerAdapter} * can support it. Typical HandlerAdapters will base the decision on the handler * type. HandlerAdapters will usually only support one handler type each. * <p>A typical implementation: * <p>{@code * return (handler instanceof MyHandler); * } * @param handler the handler object to check * @return whether or not this object can use the given handler */booleansupports(Object handler); /** * Use the given handler to handle this request. * The workflow that is required may vary widely. * @param request current HTTP request * @param response current HTTP response * @param handler the handler to use. This object must have previously been passed * to the {@code supports} method of this interface, which must have * returned {@code true}. * @throwsException in case of errors * @return a ModelAndView object with the name of the view and the required * model data, or {@code null} if the request has been handled directly */@NullableModelAndViewhandle(HttpServletRequest request,HttpServletResponse response,Object handler) throwsException; /** * Same contract as for HttpServlet's {@code getLastModified} method. * Can simply return -1 if there's no support in the handler class. * @param request current HTTP request * @param handler the handler to use * @return the lastModified value for the given handler * @see javax.servlet.http.HttpServlet#getLastModified * @see org.springframework.web.servlet.mvc.LastModified#getLastModified */longgetLastModified(HttpServletRequest request,Object handler);
HttpServletRequest와 HttpServletResponse를 받아서 ModelAndView를 반환해주는 어댑터에 대한 인터페이스를 정의한 것이 HandlerAdapter이다.
왜 이런 어댑터 인터페이스가 필요했을까?
다양한 형태의 핸들러가 있고, 각기 다른 형태에 따라 각각 다르게 처리해야하기 때문에, 다르게 처리해야하는 모든 핸들러가 스프링MVC에 들어있고, 다양한 형태의 핸들러를 다 지원할 수 있게(확장에 열려있게) 해주기 위해 스프링 MVC가 고안해놓은 인터페이스이다.
어댑터 패턴을 이해한 후에 SpringMVC의 DispatcherServlet 구현 코드를 읽어보고 doDispatch의 동작 원리를 공부해보면 좋을 것 같다!
레거시 시스템을 원하는 인터페이스로 사용 가능하게 할 수 있고, 어댑터 객체에서 적절히 구현 후 적용한다면 단순한 wrapping 이상의 효과를 볼 수 있을 것 같다.
추가로 설명하자면
선언한 컨트롤러 클래스내의 메서드를 실행하여 응답을 하는데 이때 반환 값이 String/M odelAndView 로 다양한 결과값을 반환할 수 있습니다. 하지만 Servlet에서는 무조건 ModelAndView를 받아서 이를 사용하도록 구현되었기 때문에 두 사이를 연결하기 위해 HandlerAdpater가 필요합니다.
대표적인 HandlerAdpater로 RequestMappingHandlerAdpater / HttpRequestHadlerAdapter / SimpleControllerAdapter가 존재고 이외에도 HandlerFucntionAdapter, SimpleServletHandlerAdpater가 존재합니다.
1. RequestMappingHandlerAdpater
첫번째로 **RequestMappingHandlerAdpater**는 AbstractHandlerMethodAdpater를 상속한 구현체로 @Annotation방식의 핸들러에 사용이 됩니다.