어댑터(Adapter) 패턴

어댑터 패턴은 서로 호환되지 않는 인터페이스를 가진 두 개의 클래스를 함께 작동할 수 있게 해주는 디자인 패턴입니다.
이 패턴은 기존의 클래스들을 수정하지 않고, 그들 간의 상호작용을 중개해주는 어댑터 클래스를 도입하여 호환성을 확보합니다.

구성 요소

타겟 인터페이스(Target Interface): 클라이언트가 사용하는 인터페이스를 정의합니다.
어댑터(Adapter): 타겟 인터페이스를 구현하면서 어댑티 클래스와의 상호작용을 중개합니다.
어댑티(Adaptee): 호환성이 필요한 기존 클래스로써 어댑터가 중개하는 대상입니다.

구현:

  1. 타겟 인터페이스를 정의합니다.
  2. 어댑터 클래스를 생성하고 타겟 인터페이스를 구현합니다.
  3. 어댑터 클래스 내에서 어댑티 클래스의 인스턴스를 생성하고, 타겟 인터페이스의 메서드를 어댑티 클래스의 메서드로 중개합니다.
  4. 클라이언트에서는 어댑터 객체를 사용하여 타겟 인터페이스의 메서드를 호출합니다.

장점

기존의 클래스들을 수정하지 않고도 호환성을 확보할 수 있습니다.
코드 재사용성이 증가합니다.
유지 보수성이 향상됩니다.

단점

어댑터 패턴을 사용하면 중간 단계인 어댑터 클래스가 추가되므로 약간의 성능 저하가 발생할 수 있습니다.
어댑터 클래스가 많아질 경우 코드 복잡성이 증가할 수 있습니다.

예시

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);
    }

}

결론

어댑터 패턴은 호환성이 없는 클래스들 간의 상호작용을 중개하기 위한 유용한 디자인 패턴입니다.
기존 클래스들을 수정하지 않고도 코드 재사용성과 유지 보수성을 향상시킬 수 있습니다. 그러나 어댑터 클래스 추가로 인한 성능 저하와 코드 복잡성에 주의해야 합니다.

예제는 이곳 에서 확인하실수 있습니다.