어댑터(Adapter) 패턴
디자인 패턴 ·어댑터 패턴은 서로 호환되지 않는 인터페이스를 가진 두 개의 클래스를 함께 작동할 수 있게 해주는 디자인 패턴입니다.
이 패턴은 기존의 클래스들을 수정하지 않고, 그들 간의 상호작용을 중개해주는 어댑터 클래스를 도입하여 호환성을 확보합니다.
구성 요소
타겟 인터페이스(Target Interface): 클라이언트가 사용하는 인터페이스를 정의합니다.
어댑터(Adapter): 타겟 인터페이스를 구현하면서 어댑티 클래스와의 상호작용을 중개합니다.
어댑티(Adaptee): 호환성이 필요한 기존 클래스로써 어댑터가 중개하는 대상입니다.
구현:
- 타겟 인터페이스를 정의합니다.
- 어댑터 클래스를 생성하고 타겟 인터페이스를 구현합니다.
- 어댑터 클래스 내에서 어댑티 클래스의 인스턴스를 생성하고, 타겟 인터페이스의 메서드를 어댑티 클래스의 메서드로 중개합니다.
- 클라이언트에서는 어댑터 객체를 사용하여 타겟 인터페이스의 메서드를 호출합니다.
장점
기존의 클래스들을 수정하지 않고도 호환성을 확보할 수 있습니다.
코드 재사용성이 증가합니다.
유지 보수성이 향상됩니다.
단점
어댑터 패턴을 사용하면 중간 단계인 어댑터 클래스가 추가되므로 약간의 성능 저하가 발생할 수 있습니다.
어댑터 클래스가 많아질 경우 코드 복잡성이 증가할 수 있습니다.
예시
UserDetails를 반환해주는 UserDetailsService 인터페이스의 loadUser 함수를
Account와 AccountService 클래스를 이용해 유저정보를 반환하려고한다.
target
public interface UserDetails {
String getUsername();
String getPassword();
}
public interface UserDetailsService {
UserDetails loadUser(String username);
}
Adaptee
public class Account {
private String name;
private String password;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
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) {
}
}
Adapter
public class AccountUserDetails implements UserDetails {
private final Account account;
public AccountUserDetails(Account account) {
this.account = account;
}
@Override
public String getUsername() {
return account.getName();
}
@Override
public String getPassword() {
return account.getPassword();
}
}
public class AccountToUserDetailsAdapter implements UserDetailsService {
private final AccountService accountService;
public AccountToUserDetailsAdapter(AccountService accountService) {
this.accountService = accountService;
}
@Override
public UserDetails loadUser(String username) {
Account account = accountService.findAccountByUsername(username);
UserDetails userDetails = new AccountUserDetails(account);
return userDetails;
}
}
client
public class LoginHandlerTest {
@Test
public void login_test() {
UserDetailsService userDetailsService = new AccountToUserDetailsAdapter(new AccountService());
LoginHandler loginHandler = new LoginHandler(userDetailsService);
String login = loginHandler.login("kktrkkt", "kktrkkt");
assertEquals("kktrkkt", login);
}
}
결론
어댑터 패턴은 호환성이 없는 클래스들 간의 상호작용을 중개하기 위한 유용한 디자인 패턴입니다.
기존 클래스들을 수정하지 않고도 코드 재사용성과 유지 보수성을 향상시킬 수 있습니다. 그러나 어댑터 클래스 추가로 인한 성능 저하와 코드 복잡성에 주의해야 합니다.
예제는 이곳 에서 확인하실수 있습니다.