# 어댑터 패턴

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

![imag](https://user-images.githubusercontent.com/32676275/147425566-03d5b437-be20-415b-9ce0-7476b2653f08.png)

* `Client`는 Target Interface를 사용
* `Adaptee`를 사용하기 위해 `Target`과 `Adaptee`를 연결할 `Adapter`사용

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

### 1. 예제1

```java
public class CType {
    public void getName() {
        System.out.println("C타입 포트");
    }
}
public class EightPin {
    public void getPortName() {
        System.out.println("8핀 포트");
    }
}
public class adapter extends CType {
    private EightPin eightPin;
    public adapter(EightPin eightPin) {
        this.eightPin = eightPin;
    }
    @Override
    public void getName() {
        eightPin.getPortName();
    }
}
public class Main {
    public static void main(String[] args) {
        CType port = new CType();
        CType port2 = new adapter(new EightPin());

        client(port);
        client(port2);
    }
		//클라이언트 코드는 변경되지 않는다.
    public static void client(CType port) {
        port.getName();
    }
}

//결과
C타입 포트
8핀 포트
```

![image](https://user-images.githubusercontent.com/32676275/147425587-d008037a-495f-4a29-9a07-09645af636c2.png)

* CType: `Target`
* Adapter: `Adapter`
* EightPin: `Adaptee`

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

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

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

### 2. 예제2

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

* **UserDetails**
  * username과 password 정보를 알아낼 수 있는 인터페이스
  * `Target`에 해당

```java
public interface UserDetails {
    String getUsername();
    String getPassword();
}
```

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

```java
public interface UserDetailsService {
    UserDetails loadUser(String username);
}
```

* **LoginHandler**
  * UserDetails와 UserDetailsService로 로그인을 처리하는 핸들러
  * `Client`에 해당

```java
public class LoginHandler {

    UserDetailsService userDetailsService;

    public LoginHandler(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public String login(String username, String password) {
        UserDetails userDetails = userDetailsService.loadUser(username);
        if (userDetails.getPassword().equals(password)) {
            return userDetails.getUsername();
        } else {
            throw new IllegalArgumentException();
        }
    }
}
```

**Account**

* 애플리케이션마다 (각 애플리케이션에 맞게) 만드는 일반적인 Account
* security 패키지에서 제공하는 클래스와 다르게 해당 애플리케이션에서만 사용하는 용도의 클래스이다.
* `Adaptee`에 해당

```java
public class Account {
    private String name;
    private String password;
    private String email;
  
  	// getter, setter ...
}
```

**AccountService**

* 애플리케이션마다 (각 애플리케이션에 맞게) 만드는 일반적인 AccountService
* security 패키지에서 제공하는 클래스와 다르게 해당 애플리케이션에서만 사용하는 용도의 클래스이다.
* `Adaptee`에 해당

```java
public class AccountService {

    public Account findAccountByUsername(String username) {
        Account account = new Account();
        account.setName(username);
        account.setPassword(username);
        account.setEmail(username);
        return account;
    }

    public void createNewAccount(Account account) {

    }

    public void updateAccount(Account account) {

    }

}
```

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

우리 애플리케이션의 `Account`와 `AccountService`는 `Adaptee`에 해당한다.

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

#### **AccountUserDetailsService**

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

1. UserDetailsService 인터페이스를 implements
2. Adaptee에 해당하는 AccountService를 필드로 가지고 사용
3. loadUser()를 Override할 때, AccountService를 사용
4. 이 때, `AccountService`는 `UserDetails`와 상관없는 `Account`를 넘겨주기 때문에 이를 다시 `UserDetails`로 변환해주는 어댑터가 필요

```java
public class AccountUserDetailsService implements UserDetailsService {

    private AccountService accountService;

    public AccountUserDetailsService(AccountService accountService) {
        this.accountService = accountService;
    }

    @Override
    public UserDetails loadUser(String username) {
        return new AccountUserDetails(accountService.findAccountByUsername(username));
    }
}
```

####

#### **AccountUserDetails**

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

```java
public class AccountUserDetails implements UserDetails {

    private Account account;

    public AccountUserDetails(Account account) {
        this.account = account;
    }

    @Override
    public String getUsername() {
        return account.getName();
    }

    @Override
    public String getPassword() {
        return account.getPassword();
    }
}
```

```java
public class App {

    public static void main(String[] args) {
        AccountService accountService = new AccountService();
        UserDetailsService userDetailsService = new AccountUserDetailsService(accountService);
        LoginHandler loginHandler = new LoginHandler(userDetailsService);
        String login = loginHandler.login("solar", "solar");
        System.out.println(login); //solar
    }
}
```

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

`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...)

```java
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**

* 핸들러 : 요청을 처리하고 응답을 반환하는 객체로 컨트롤러 클래스가 될수도 있고 메서드가 될 수도 있다.  &#x20;
* 핸들러 어댑터 : 우리가 작성하는 다양한 형태의 핸들러 코드를 스프링 MVC가 실행할 수 있는 형태로 변환해주는 어댑터용 인터페이스.

```java
public class AdapterInSpring {
    public static void main(String[] args) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        HandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
    }
}
```

#### `DispathcerServlet`

* `doDispatch()`

```
// 해당 핸들러를 처리할 수 있는 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`를 찾아서 반환한다.

```java
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter adapter : this.handlerAdapters) {
         if (adapter.supports(handler)) {
            return adapter;
         }
      }
   }
   throw new ServletException("No adapter for handler [" + handler +
         "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
```

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

#### HandlerAdapter

```java
public interface HandlerAdapter {

   /**    * 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
    */boolean supports(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}.
    * @throws Exception 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
    */@Nullable
   ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

   /**    * 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
    */long getLastModified(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`방식의 핸들러에 사용이 됩니다.

```java
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    this.checkRequest(request);
    ModelAndView mav;
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized(mutex) {
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        mav = this.invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader("Cache-Control")) {
        if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            this.prepareResponse(response);
        }
    }

    return mav;
}

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ServletWebRequest webRequest = new ServletWebRequest(request, response);

    ModelAndView var15;
    try {
        //생략...

        var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        webRequest.requestCompleted();
    }

    return var15;
}
```

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

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

#### 2. HttpRequestHandlerAdapter

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

```java
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    ((HttpRequestHandler)handler).handleRequest(request, response);
    return null;
}
```

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

#### 3. **SimpleControllerHandlerAdpter**

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

```java
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return ((Controller)handler).handleRequest(request, response);
}
```

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nelmm.gitbook.io/nelmm/design-pattern/undefined-1/undefined.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
