일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 몽고 인덱스
- 크롤링
- docker
- 결제서비스
- piplining
- 레디스 동시성
- 백준
- spring event
- 카카오
- AWS
- 프로그래머스
- ai agent
- 이분탐색
- 쿠키
- 구현
- 셀러리
- 트랜잭샨
- 추천 검색 기능
- 디버깅
- 누적합
- 아키텍쳐 개선
- 완전탐색
- langgraph
- JPA
- ipo 매매자동화
- next-stock
- jwt 표준
- 검색어 추천
- gRPC
- BFS
- Today
- Total
코딩관계론
디자인패턴 - 어댑터 패턴 본문
어댑터 패턴을 사용하는 이유
클라이언트가 사용하는 인터페이스와 내가 사용하는 인터페이스가 서로 다를 때, 이 두 인터페이스를 연결하여 호환성을 확보하기 위해 어댑터 패턴을 사용합니다. 특히, 오래된 코드나 타사 라이브러리를 새로운 시스템에 통합할 때 유용합니다. 어댑터를 통해 레거시 코드의 인터페이스를 변경하지 않고도 새로운 시스템과 통합할 수 있습니다.
클래스 설명
어댑터 패턴의 구조는 아래의 그림과 같다. 클라이언트 코드가 의존하는 인터페이스를 어댑터가 어뎁티를 이용해 구현해줌으로써 3rd-party가 제공한 기능을 커스텀하여 사용할 수 있게 된다.
어댑터 패턴 구현 방법을 소개하기 전에 어떤 클래스가 클라이언트가 의존하는 클래스인지, 어떤 클래스가 Adaptee인지 설명하겠습니다.
클라이언트가 의존하는 인터페이스
아래 코드는 클라이언트가 의존하는 인터페이스입니다. 이 클래스는 외부에서 제공하는 클래스로 수정이 불가능합니다.
//외부가 제공해주는 클래스로 절대 수정이 불가능함
public class LoginHandler {
UserDetailService userDetailService;
public LoginHandler(UserDetailService userDetailService) {
this.userDetailService = userDetailService;
}
public String login(String username, String password) {
UserDetails userDetails = userDetailService.loadUser(username);
if (userDetails.getPassword().equals(password)) {
return userDetails.getUsername();
}else{
throw new IllegalArgumentException();
}
}
}
//다른 파일
public interface UserDetailService {
UserDetails loadUser(String username);
}
//다른 파일
public interface UserDetails {
String getPassword();
String getUsername();
}
Adaptee 클래스
아래는 우리가 구현한 애플리케이션의 클래스, 즉 Adaptee입니다.
package org.example.adapter.security;
public class Account {
private String username;
private String password;
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
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 {
Map<String, Account> accounts = new HashMap<>();
public Account findAccountByUsername(String username) {
Account account = accounts.get(username);
return account;
}
public void createNewAccount(Account account){
accounts.put(account.getUsername(), account);
}
}
어댑터 패턴 구현 방법
1. Client가 구현하는 Interface를 구현해 Adapter를 생성해야 한다.
public class AccountUserDetailAdapter implements UserDetails{ ;
private Account account;
public AccountUserDetailAdapter(Account account) {
this.account = account;
}
@Override
public String getPassword() {
return account.getPassword();
}
@Override
public String getUsername() {
return account.getUsername();
}
}
public class AccountUserDetailServiceAdpater implements UserDetailService{
AccountService accountService;
public AccountUserDetailServiceAdpater(AccountService accountService) {
this.accountService = accountService;
}
@Override
public UserDetails loadUser(String username) {
Account accountByUsername = accountService.findAccountByUsername(username);
AccountUserDetailAdapter accountUserDetail = new AccountUserDetailAdapter(accountByUsername);
return accountUserDetail;
}
}
어댑터 패턴을 사용하여 LoginHandler를 호출하는 메인 코드를 설명드리겠습니다. 아래의 코드에서는 LoginHandler에 어댑터 클래스를 주입함으로써, 기존의 3rd-party 서비스와 새로 만든 서비스를 통합하여 사용할 수 있습니다.
public class App {
public static void main(String[] args) {
AccountService accountService = new AccountService();
AccountUserDetailServiceAdpater accountUserDetailService = new AccountUserDetailServiceAdpater(accountService);
LoginHandler loginHandler = new LoginHandler(accountUserDetailService);
loginHandler.login("Bae", "jaewan");
}
}
어댑터 패턴의 단점
어뎁터 클래스가 생김으로 클래스 간의 복잡도가 증가될 수 있다.
스프링에서의 팩토리 패턴은?
스프링에서는 mvc패턴이 Adpater패턴을 이용해서 구현됐다.
Adaptee 클래스
커스텀한 핸들러는 다음과 같이 작성됩니다. 이는 Adaptee 역할을 합니다:
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("do-something");
return new ModelAndView("new-form");
}
}
스프링에서 제공하는 핸들러 어댑터
스프링은 다음과 같은 핸들러 어댑터를 제공하여, 우리의 커스텀 핸들러를 호출할 수 있게 합니다. 여기서 구현한 HandlerAdapter가 Client가 의존하게 되는 Interface입니다.
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public SimpleControllerHandlerAdapter() {
}
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller)handler).handleRequest(request, response);
}
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified lastModified) {
return lastModified.getLastModified(request);
} else {
return -1L;
}
}
}
//최종적으로 Client가 의존하는 Interface
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/** @deprecated */
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
DispatcherServlet에서의 호출
아래의 코드를 통해 DispatcherServlet이 HandlerAdapter를 사용하여 클라이언트가 기대하는 인터페이스와 우리가 구현한 커스텀 핸들러를 연결합니다.
//DispatcherServlet클래스의 do_dispatch의 일부분
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
최종적인 호출흐름은 아래 그림과 같다.
[참고자료]
https://www.inflearn.com/course/lecture
학습 페이지
www.inflearn.com
'개발 > Java' 카테고리의 다른 글
스프링의 IoC(Inversion of Control)과 DI(Dependency Injection) (0) | 2024.07.10 |
---|---|
디자인 패턴 - 팩토리 패턴 (0) | 2024.07.08 |
디자인 패턴 - 싱글톤 패턴 (0) | 2024.07.07 |
클러스터링과 논클러스트링이란? (0) | 2024.07.03 |
DB Connection Pool은 왜 필요할까? (0) | 2024.07.02 |