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를 통해서 변경이 필요하다.