Java/디자인 패턴
[디자인 패턴] 어댑터 패턴
태감새
2023. 4. 22. 18:36
코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
어댑터
기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴
- 클라이언트가 사용하는 인터페이스를 따르지 않는 기존 코드를 재사용할 수 있게 해준다.

클라이언트는 Target 인터페이스 사용중
Adaptee라는 Target과 연관이 없는 클래스를 Target으로 연결시켜주는 패턴
예제

Target
UserDetails(I)
- User의 detail을 가져올 수 있는 인터페이스
public interface UserDetails {
String getUsername();
String getPassword();
}
UserDetialsService(I)
- User와 관련된 서비스 메서드를 가진 인터페이스
public interface UserDetailsService {
UserDetails loadUser(String username);
}
LoginHandler
- 로그인 요청을 처리하는 핸들러
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();
}
}
}
Adaptee
Account
- Account 엔티티
@Getter
@Setter
public class Account {
private String name;
private String password;
private String email;
}
AccountService
- Account와 관련된 서비스 메서드를 가진 클래스
- 메서드명은 findAccountByUsername이지만 계정을 생성하는 로직을 가지고 있다.
- 패턴 이해가 목적이므로 우선 모든 정보를 username으로 계정을 생성한다고 알고 넘어가자
public class AccountService {
public Account findAccountByUsername(String username) {
Account account = new Account();
account.setName(username);
account.setPassword(username);
account.setEmail(username);
return account;
}
}
Adapter
- 기존에 클라이언트는 LoginHandler로 로그인을 진행
- 이를 위해서는 UserDetails, UserDetailsService의 구현체가 필요하다.
- 현재 예시에 구현체는 없음
- Account를 LoginHandler에서 사용하기 위해서는 UserDetails, UserDetailsService의 구현체가 필요
AccountUserDetails
- UserDetails를 구현해서 필요 메서드를 오버라이드
- AccountUserDetails 객체 생성 시 Account객체를 입력받아 필요한 정보 입력
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();
}
}
AccountUserDetailsService
- UserDetailsService를 구현해서 필요 메서드를 오버라이드
- AccountUserDetailsService 객체 생성 시 UserDetailsService를 입력받아 필요한 정보 입력
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));
}
}
App
- AccountService객체를 이용해서 LoginHandler사용할 수 있게 됨
- 개인적으로 구현 내용은 조금 이상하다고 생각됨
- 세부 내용보다는 Account(Adaptee)가 User(Target)으로 사용된다는 점에 초점
- 개인적으로는 start~end까지 메서드로 묶어서 Adapter과정을 숨기는 것도 좋다고 생각됨
public class App {
public static void main(String[] args) {
// start
AccountService accountService = new AccountService();
UserDetailsService userDetailsService = new AccountUserDetailsService(accountService);
LoginHandler loginHandler = new LoginHandler(userDetailsService);
// end
String login = loginHandler.login("Han", "Han");
System.out.println(login);
}
}
장/단점
장점
- 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어서 사용가능
- 기존 코드의 작업과 변환작업을 가진 인터페이스의 구현체를 다른 클래스로 분리하여 관리 가능
단점
- 복잡도가 증가
- 상황에 따라서는 따로 클래스를 만들지 않고 바로 구현해서 사용가능
개인적인 의견
솔직히 이번 예시는 내용이 좋지는 않다고 생각한다. 뭔가 어댑터 패턴을 설명하기 위해서 끼워맞춰진 느낌이랄까. 하지만 앞서 언급했다시피 이 강의 목적은 패턴을 익히는 것이기 때문에 수정없이 넘어갔다.
SpringMVC를 공부할 때 DispatcherServlet에서 Mapping에 맞는 핸들러를 찾고 왜 바로 핸들러를 사용하지 않고 HandlerAdapter를 사용했는지 와닿지 않았는데 패턴을 이해하니 그 이유를 알 수 있었다. DispatcherSerlvet은 하나인데 핸들러가 각기다른 타입으로 반환하면 받을수가 없으므로 Adpater를 통해서 변경이 필요하다.