3 Apr 2022

[Java] Adapter 패턴

Adapter 패턴

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

  • 클라이언트가 사용하는 인터페이스를 따르지 않는 기존 코드를 재사용할 수 있게 해준다.

어댑터-1


기존의 코드를 인터페이스를 따르도록 하여 재사용할 수 있도록 하는 예시로 spring security가 있다.

  • Oauth2.0을 구현할때 구글,네이버,카카오등 구현 방식이 조금씩 다를 수 있고 로그인에서 사용자(Account)를 불러오고, 활용하는 데이터가 다를 수 있다. 이때 어댑터 패턴을 사용하여 공통화된 인터페이스로 감싸서 사용하는 예시 코드를 구현해보자


상황: 사용자는 Account 도메인과, 이에대한 로직을 갖고 있는 AccountService를 이미 사용중이고 코드변경은 불가하다. 하지만 spring security를 사용하여 다른 구글,네이버등의 로그인 서비스를 이용하고 싶은 상황이며 이를 위해 공통화된 기능 구현이 필요하다.

  • client에서 아래처럼 사용하고 싶은 상황
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("홍길동","홍길동");
        System.out.println(login);
    }
}


  • 구현할 interface
public interface UserDetailsService {
    UserDetails loadUser(String name);
}
public interface UserDetails {
    String getUsername();
    String getPassword();
}


  • Account : 기존에 사용하고 있던 유저 도메인 코드
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 class AccountUserDetailsService implements UserDetailsService {
    private AccountService accountService;

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

    @Override
    public UserDetails loadUser(String name) {
        Account account=accountService.findAccountByUsername(name);
        return new AccountUserDetails(account);// UserDetails 를 구현하는 AccountUserDetails로 account를 주입하여 반환
    }
}
public class AccountUserDetails implements UserDetails{
    private Account account;

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

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

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


  • UserDetailsService를 통해 로그인 서비스를 동작시킨다.
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();
        }
    }
}

최종적으로 LoginHandler를 통해 해당 UserDetailsService를 구현한 구현체를 의존성 주입을 받으면서 기존의 코드를 수정하지 않고 클라이언트가 사용하는 인터페이스 구현체를 사용하게 만들었다.


어댑터 패턴의 장단점

장점

  • 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용할 수 있다.
  • 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  • 새 클래스가 생겨 복잡도가 증가할 수 있다. 경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택이 될 수도 있다.

Tags:
0 comments