어댑터 패턴
- 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴
- 서로 다른 인터페이스를 가진 두 클래스가 협업할 수 있도록 중간에 어댑터를 두어 호환성을 제공하는 구조적 디자인 패턴
- 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하여, 호환되지 않는 인터페이스를 가진 클래스들을 함께 동작하도록 지원
- 주로 레거시 코드를 새로운 시스템에서 사용하고자 할 때 유용
- 해외여행 필수품인 플러그 어댑터를 생각하면 이해하기 쉬움

주요 구성 요소
1. Target 인터페이스
- 클라이언트가 사용하려고 하는 인터페이스
2. Adapter
- 타겟 인터페이스를 구현하고 어댑티(Adaptee)의 인터페이스를 호출하여 중간 역할
3. Adaptee
- 변환되어야 할 기존 클래스
4. Client
- 타겟 인터페이스를 사용하는 객체
어댑터 패턴 구현 예시 (Spring Security)
- Spring Security에서 어댑터 패턴을 활용하여 기존의 사용자 정보 객체를 UserDetails 인터페이스와 호환되도록 만들 수 있음
1. UserDetails 인터페이스
- Spring Security에서 사용자 정보를 표현하는 인터페이스
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface UserDetails extends Serializable { | |
Collection<? extends GrantedAuthority> getAuthorities(); | |
String getPassword(); | |
String getUsername(); | |
default boolean isAccountNonExpired() { | |
return true; | |
} | |
default boolean isAccountNonLocked() { | |
return true; | |
} | |
default boolean isCredentialsNonExpired() { | |
return true; | |
} | |
default boolean isEnabled() { | |
return true; | |
} | |
} |
2. 애플리케이션에서 사용하는 User 클래스 (Adaptee)
- 기존 시스템에서 사용되고 있는 사용자 정보 클래스
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Data | |
public class User { | |
private String username; | |
private String password; | |
private String role; | |
} |
3. UserDetailsAdapter 클래스 (Adapter)
- 기존 사용자 클래스를 UserDetails 인터페이스와 호환되도록 변환하는 어댑터 클래스
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UserDetailsAdapter implements UserDetails { | |
private User user; | |
public UserDetailsAdapter(User user) { | |
this.user = user; | |
} | |
@Override | |
public Collection<? extends GrantedAuthority> getAuthorities() { | |
return Collections.singletonList(new SimpleGrantedAuthority(user.getRole())); | |
} | |
@Override | |
public String getPassword() { | |
return user.getPassword(); | |
} | |
@Override | |
public String getUsername() { | |
return user.getUsername(); | |
} | |
@Override | |
public boolean isAccountNonExpired() { | |
return true; | |
} | |
@Override | |
public boolean isAccountNonLocked() { | |
return true; | |
} | |
@Override | |
public boolean isCredentialsNonExpired() { | |
return true; | |
} | |
@Override | |
public boolean isEnabled() { | |
return true; | |
} | |
} |
정리
- UserDetailsAdapter 클래스는 UserDetails 인터페이스를 구현하고, 기존의 User 객체를 내부에 두어, Spring Security에서 기대하는 UserDetails 인터페이스로 변환시키며 이를 통해 Spring Security와 기존 사용자 클래스를 호환시킬 수 있음
- 이처럼 어댑터 패턴은 기존 클래스와 새로운 인터페이스를 연결해 주는 중요한 역할을 하며, Spring Security의 UserDetails 인터페이스를 사용할 때도 유용하게 활용할 수 있음
어댑터 패턴 장단점
장점
- 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 재사용 가능 (SOLID 원칙의 OCP 원칙)
- 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리 가능 (SOLID 원칙의 SRP 원칙)
단점
- 다른 디자인 패턴들과 마찬가지로 클래스가 늘어남에 따라 관리 포인트가 늘어나 복잡도가 증가할 수 있음
- 이를 방지하기 위해 기존 코드가 해당 인터페이스를 구현하도록 수정하는 방법도 있지만 이는 SOLID의 단일 책임 원칙을 위반하는 행위
- 트레이드오프 관계를 고려하여 설계할 필요성 있음
실무에서 쓰이는 어댑터 패턴
1. Spring Security
- 앞서 예시에서 간략하게 설명했으므로 생략
2. 자바 컬렉션
- Arrays.asList(T ...): 가변인자인 배열을 매개변수로 넘기고 List를 반환받음
- Collections.enumeration(String): 문자열을 매개변수로 넘기고 Enumeration을 반환받음
- Collections.list(Enumeration): Enumeration을 매개변수로 넘기고 List를 반환받음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// collections | |
List<String> strings = Arrays.asList("a", "b", "c"); // 전달해준 것은 가변인자인 배열이지만 List로 변환해줌 | |
Enumeration<String> enumeration = Collections.enumeration(strings); // List를 넘겨줬지만 Enumeration으로 반환 받음 | |
ArrayList<String> list = Collections.list(enumeration); // Enumeration을 받아서 List로 변환 |
3. Spring MVC의 HandlerAdapter
- 다양한 형태의 Handler 코드를 스프링 MVC가 실행할 수 있는 형태로 변환해 주는 어댑터용 인터페이스
- Spring MVC에서 여러 가지 HandlerAdapter 구현체가 제공됨
- SimpleControllerHandlerAdapter: Controller 인터페이스를 구현한 핸들러를 처리
- RequestMappingHandlerAdapter: @RequestMapping 어노테이션을 사용하는 핸들러 메서드를 처리

DispatcherServlet 관점에서의 어댑터 패턴
- DispatcherServlet은 Spring MVC의 프론트 컨트롤러로 모든 HTTP 요청을 처리하고 적절한 핸들러를 전달하는 역할
- DispatcherServlet은 여러 가지 핸들러를 처리할 수 있어야 하며 이때 어댑터 패턴을 사용하여 다양한 유형의 핸들러를 통합적으로 관리
- Target 인터페이스: HandlerAdatper 인터페이스
- Adaptee: 다양한 유형의 핸들러로 @Controller나 Controller 인터페이스를 구현한 클래스
- Adatper: HandlerAdapter 인터페이스를 구현한 클래스들
- Client: DispatcherServlet으로 다양한 핸들러를 일관된 방식으로 호출
DispatcherServlet 일부 발췌
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { | |
// 1. 요청 매핑 | |
HandlerExecutionChain mappedHandler = getHandler(request); | |
if (mappedHandler == null) { | |
noHandlerFound(request, response); | |
return; | |
} | |
// 2. 적절한 HandlerAdapter 조회 | |
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); | |
// 3. 핸들러 실행 | |
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler()); | |
// 4. 결과 반환 및 뷰 렌더링 | |
if (mv != null) { | |
render(mv, request, response); | |
} | |
} | |
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { | |
for (HandlerAdapter ha : this.handlerAdapters) { | |
if (ha.supports(handler)) { | |
return ha; | |
} | |
} | |
throw new ServletException("No adapter for handler [" + handler + "]"); | |
} |
동작 과정
- DispatcherServlet이 HTTP 요청 수신
- HandlerMapping을 사용하여 요청을 처리할 핸들러 조회
- DispatcherServlet은 HandlerAdapter 목록을 순회하며 supports 메서드를 통해 주어진 핸들러를 처리할 수 있는 HandlerAdapter 조회
- 적절한 HandlerAdapter를 handle 메서드를 통해 실행
- ModelAndView를 통해 결과를 반환하고 ViewResolver를 사용하여 뷰를 렌더링
참고
코딩으로 학습하는 GoF의 디자인 패턴 - 백기선 강사님
반응형
'Design Pattern' 카테고리의 다른 글
[디자인 패턴] 컴포짓 패턴 (Composite Pattern) (0) | 2024.06.22 |
---|---|
[디자인 패턴] 브릿지 패턴 (Bridge Pattern) (0) | 2024.06.22 |
[디자인 패턴] 프로토타입 패턴 (Prototype Pattern) (0) | 2024.06.22 |
[디자인 패턴] 빌더 패턴 (Builder Pattern) (0) | 2024.06.21 |
[디자인 패턴] 추상 팩토리 패턴 (Abstract Factory Pattern) (0) | 2024.06.18 |