Java/Design Pattern

[디자인 패턴] 구조 패턴 - 어댑터 패턴 (Adapter Patterns)

민돌v 2023. 2. 16. 19:24
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 


✔️ 어댑터 패턴

  • 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴
  • 서로 다른 인터페이스를 구현하는 코드들을, 서로 상호 호환되도록 구조를 변경해주는 어댑터를 제공하는 패턴 입니다.
  • 즉, 클라이언트가 사용하는 인터페이스를 따르지 않는 기존 코드를, 재사용 가능하도록 설계할 수 있습니다.

 

 


✔️ 어댑터 패턴 구현방법

  • 어댑터 패턴은, 제공되는 클래스를 건드릴 수 없는 경우 → 즉, 써드파티에서 제공하는 클래스거나, 엄청난 레거시 코드를 내 코드와 융합할 떄 사용할 수 있습니다.
  • 고로 중점은, 기존의 코드를 건드리지 않고, 나의 코드(Adaptee)와 융합한 클래스(Adapter)를 구현하는 것 입니다.

 

📌 간단하게 로그인 기능을 제공하는 써드파티 라이브러리가 있다고 가정해보겠습니다.

@AllArgsConstructor
public class LoginHandler {

    UserDetailsService userDetailsService;

    public String login(String username, String password) {

        UserDetails userDetails = userDetailsService.loadUser(username);

        if (userDetails.getPassword().equals(password)) {
            return userDetails.getUserName();
        }

        throw new IllegalArgumentException();
    }

}

public interface UserDetailsService {

    UserDetails loadUser(String userName);
}


public interface UserDetails {

    String getUserName();
    String getPassword();
}

 

📌 이제 써드파티를 사용해야하는 Adaptee 역할을 하는 코드가 정의되어있다고 가정합니다.

//Account 와 AccountService 는 해당 app 단위에서만 사용하는 일정의 adaptee
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) {

    }
}

@Getter
@Setter
public class Account {

    private String name;
    private String password;
    private String email;
}

 

📌 그럼 Adaptee가 변경할 수 없는 제공받는 코드를 사용할 수 있도록 Adapter를 구현해서 기존 코드를 재사용 할 수 있습니다.

public class AccountUserDetailsService implements UserDetailsService {

    AccountService accountService;

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

    @Override
    public UserDetails loadUser(String userName) {
        Account account = accountService.findAccountByUsername(userName);

        return new AccountUserDetails(account);
    }
};

public class AccountUserDetails implements UserDetails {

    private Account account;

    public AccountUserDetails(Account account) {
        this.account = account;
        this.account = account;
    }
    @Override
    public String getUserName() {
        return this.account.getName();
    }

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

 

 

📌 클라이언트 코드

  • 실 사용하는 클라이언트 코드에서는 어뎁터 class 를 사용하여 외부 라이브러리도 사용하면서 기존 코드도 재사용하는 구조가 되어집니다.
  • 또한, 새로운 Adaptee를 사용한다고 하더라도 기존 코드가 변경되지않고 Adapter를 새롭게 생성하여 확장이 가능한 구조가 되어집니다.
/**
*  어댑터를 별도으 클래스로 만들어서 사용할 때,
*  써드파티에서 들어오는 라이브러리나, 종속성이 강하게 묶여 기존의 코드를 수정할 수 없는경우 (어댑티를 수정할 수 없는 경우)
*  유용하게 사용할 수 있다.
*
*  -> 내가 직접 코드를 변경할 수 있다면
*  (Account, AccountService, 어댑티를 직접 수정)
*/

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("hmin", "hmin");
        System.out.println("login = " + login);
    }
}

 


✔️ 어댑터 패턴 장단점

장점

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

 

단점

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

 


👏🏻 추가적으로 참고만 하자면, 팀장님과 스터디를 하다가 예시에 대해 고민해봤는데

예전에 Test를 위해 DB 종속성을 끊고 항상 성공하는 테스트케이스를 만들기 위해서 JpaRepository 인터페이스의 구현체를 직접 만들어서 사용한적이 있습니다.

완벽하지는 않지만 이런 느낌이 어댑터 패턴에서 추구하는 것이 아닐까.. 그런 생각이 들어 첨부해 봅니다!!

https://thalals.tistory.com/395

 

[Spring] service 단위 테스트 하기 - DB 와 독립된 테스트 환경 구축 (service unit test)

📌 이번 포스팅의 목적은 "Repository 에 의존하지 않는 Service Test" 와 "항상 성공하는 테스트 환경" 을 구축해보는 것입니다! 지금 사내에서는 service : repository = 1:1 구조로 가고있는데 repository 에서

thalals.tistory.com

 

 

끝!

 

 

반응형