본문 바로가기
Computer Sience/Desgin Pattern

[Design Pattern] 어댑터 (Adapter) 패턴

by 제우제우 2024. 10. 22.

어댑터 (Adpater) 패턴 - 구조적인 패턴

어댑터?

전자기기 규격이 110v 220v라는 말을 들어본 적 있을 것이다. 

전자기기가 220v 용이면 110v 콘센트에서는 사용 불가능하다. 

하지만 중간에 전압 변환 어댑터를 사용하면 110v 콘센트에서 나오는 전기를 220v로 바꿔서 전자기기에 사용이 가능하다.

디자인 패턴 Adpater Pattern 또한 이런 성질을 가지는 구조이다.

 

소프트웨어 측면 Adapter? 

어댑터 패턴(Adapter)은 서로 호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 

중간에서 변환 역할을 해주는 디자인 패턴이다. 

어댑터 패턴을 사용하면 기존 코드를 수정하지 않고도 호환되지 않는 인터페이스를 결합할 수 있다. 

어댑터 패턴의 주요 구성 요소

클라이언트(Client)

기존 인터페이스를 사용하고자 하는 코드이다.

 

타깃(Target)

클라이언트가 기대하는 인터페이스 

 

어댑터(Adpater)

타깃 인터페이스를 구현하고, 적응해야 할 클래스(Adaptee)의 인터페이스를 변환해주는 클래스이다.

 

적응 대상(Adaptee)

클라이언트가 사용하려는 실제 객체로, 타깃 인터페이스와 맞지 않는 인터페이스를 가지고 있다. 

어댑터 (Adpater) 패턴 - Before

public interface UserDetails {
    String getUsername();
    String getPassword();
}

public interface UserDetailsService {
    UserDetails loadUser(String username);
}

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();
        }
        throw new IllegalArgumentException();
    }
}

@Getter @Setter
public class Account {
    private String name;
    private String password;
    private String email;
}

public class AccountService {
    public Account findAccountByUsername(String username){
        return null;
    }
    public void createNewAccount(Account account){
    }
    public void updateAccount(Account account){
    }
}

 

UserDetails / UserDetailsService / LoginHandler

 

LoginHandler는 UserDetailsService를 통해서 사용자를 조회하고 

UserDetails 객체를 받는다.

 

Spring Security 프레임워크의 구조가 이와 유사하다.

LoginHandler → SecurityFilterChainProxy 필터 중 일부

 

Account / AccountService

현재 애플리케이션에서 유저 정보의 저장 형태(엔티티) / ex) DB or 인메모리에서 유저를 조회하는 서비스 

 

호환성 x

LoginHandler는  UserDetailsService를 통해서 유저를 조회하는데

이는 우리가 사용하는 AccountService와 호환되지 않는다. 

또한 UserDetailsService가 조회 이후 반환하는 UserDetails와 Account와 호환되지 않는다.

 

어댑터 패턴 도입 

UserDetailsService를 구현하는 AccountUserDetailsService 클래스를 어댑터로 사용하자.

AccountUserDetailsService는 내부에서 AccountService를 사용 

 

유저 조회를 하고 반환 타입 또한 UserDetails를 구현하는 AccoutUserDetails 클래스를 어댑터로 사용하여 반환되게 만들어 보자.

AccountUserDetails 또한 내부에서 Account를 가진다. 

 

이와 같은 방식으로 어댑터 패턴을 활용하면, 호환되지 않는 두 시스템(스프링 시큐리티 구조와 애플리케이션의 사용자 관리 시스템)을 연결할 수 있다. 

어댑터 (Adpater) 패턴 - After

@RequiredArgsConstructor
public class AccountUserDetails implements UserDetails {
    private final Account account;
    @Override
    public String getUsername() {
        return account.getName();
    }
    @Override
    public String getPassword() {
        return account.getPassword();
    }
}

@RequiredArgsConstructor
public class AccountUserDetailsService implements UserDetailsService {
    private final AccountService accountService;
    @Override
    public UserDetails loadUser(String username) {
        Account account = accountService.findAccountByUsername(username);
        return new AccountUserDetails(account);
    }
}

 

AccountUserDetails & AccountUserDetailsService 

어댑터 패턴의 어댑터 역할을 하는 두 클래스이다. 

AccountUserDetails는 UserDetails & Account를 연결

AccountUserDetailsService는 UserDetailsService & AccountService를 연결 

 

정리 

이 2개의 어댑터 클래스를 통해서 

클라이언트 코드(LoginHandler)와 기존 애플리케이션의 유저 조회 로직이 하나도 바뀌지 않고 

시큐리티 프레임워크를 도입할 수 있게 되었다. 

 

여기서 핵심은 클라이언트 코드가 전혀 바뀌지 않았다는 것이다. 

또한 각 클래스의 역할이 정확하다. 

→ SRP가 잘 지켜지고 있다. 

어댑터 (Adpater) 패턴 - 정리

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

서로 호환되지 않는 인터페이스를 연결한다. 

 

장점

기존 코드를 변경하지 않고 인터페이스 구현체를 만들어 재사용할 수 있다. 

기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다. 

 

단점

새 클래스가 생겨 복잡도가 증가할 수 있다. 

경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택이 될수도 있다. 

하지만 해당 방식은 SRP 측면을 잘 고려해야 한다. 

 

내 생각 

이번 예시인 애플리케이션 유저 조회 서비스가 시큐리티의 패키지를 상속 받는다면 SRP 측면에서 봤을 때 좋지 않다고 생각한다. 

어댑터 (Adpater) 패턴 - 적용 사례

자바

public class AdapterInJava {
    public static void main(String[] args) {
        // Collections
        List<String> strings = Arrays.asList("a", "b", "c");
        // Iterator 유사 -> 차이점: 요소 삭제 불가능
        Enumeration<String> enumeration = Collections.enumeration(strings);
        while (enumeration.hasMoreElements()) {
            String element = enumeration.nextElement();
            System.out.println(element);
        }

        // io
        try(InputStream is = new FileInputStream("input.txt");
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader reader = new BufferedReader(isr)){
            while (reader.ready()){
                System.out.println(reader.readLine());
            }
        }
        catch (IOException e){
            throw new RuntimeException(e);
        }
    }
}

 

파일 열기: FileInputStream을 사용하여 파일을 바이트 스트림으로 연다. 
문자 변환: InputStreamReader를 통해 바이트를 문자 스트림으로 변환한다. 
버퍼링: BufferedReader를 사용하여 성능을 향상시킨다.

줄 단위 읽기: while 루프와 readLine() 메서드를 통해 파일을 한 줄씩 읽는다.

 

문자열(파일명) → FileInputStream 어댑터 패턴 

다른 과정 또한 어댑터 패턴으로 볼 수 있다. 

스프링

스프링 MVC의 HandlerAdapter

 

참고자료

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

코딩으로 학습하는 GoF의 디자인 패턴 강의 | 백기선 - 인프런

백기선 | 디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를

www.inflearn.com