클라이언트가 사용하는 인터페이스가 전혀 다르며 정해져 있고, 기존의 코드를 호환하여 재사용할 수 있도록 구현체로 변경해주는 패턴.
클라이언트는 타겟 인터페이스만으로 사용하고, 구현체를 담당하는 Adaptee와 타겟 사이의 Adapter를 활용한다.
Security package는 다른 애플리케이션에서도 사용할 수 있지만, Account 및 Service는 특정 애플리케이션에서만 사용한다. 즉, Security package는 타겟에 해당하며, Account와 관련한 특정 애플리케이션에서 사용하는 코드는 Adaptee에 해당한다. 따라서 두 코드들을 호환이 가능하도록 Adapter를 구현해야하 한다.
public class LoginHandler {
//target
UserDetailService userDetailService;
public LoginHandler(UserDetailService userDetailService) {
this.userDetailService = userDetailService;
}
public String login(String username, String password) {
//target
UserDetails userDetails = userDetailService.loadUser(username);
if(userDetails.getPassword().equals(password)) {
return userDetails.getUsername();
} else {
throw new IllegarArgumentException();
}
}
}
어댑터를 만드는 방법은 각 인터페이스 구성요소에 임의로 수정할 수 없는 상황에 대해 별도의 클래스를 구현하거나, 타겟 인터페이스를 상속하여 직접 구현하는 방법으로 구분할 수 있다.
직접 구현하는 어댑터
장점 : 새로운 클래스를 만들지 않아 코드의 복잡도를 줄일 수 있다.
단점 : 단일 체계의 원칙의 관점에서는 맞지 않고, 기존의 코드를 변경한다.
Open-Closed Principal에 가까운 패턴이다.List<String> strings = Arrays.asList("a", "b", "c");
배열을 리스트로 변경하는 경우, 상이한 인터페이스를 변환하는 경우에 적용된다.
파라미터를 Collection으로 받으며 enumeration으로 변경하는 동작을 수행한다.
Enumeration<String> enmeration = Collections.enumeration(string);
ArrayList<String> list = Collections.list(enumeration);
// String -> InputStream
// InputStream -> InputStreamReader
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);
}
}
예제와 비슷함
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hi";
}
}
DispatcherServlet을 보면 Object 타입을 받아 Handler로 보내 요청을 처리하고, 모델과 View가 나오며 데이터를 넣어 렌더링을 한다.
Handler는 요청을 처리하며, 구성에 따라 다른 Handler Adapter를 사용하게 된다. Handler는 이벤트에 따라 다르게 처리하고, Handler Adaptersms 다양한 형태의 Handler를 지원할 수 있도록 Spring MVC가 고안한 interface이다.