

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();
}
}
}
public interface UserDetails {
String getUsername();
String getPassword();
}
public interface UserDetailsService {
UserDetails loadUser(String username);
}
public class Account {
private String name;
private String password;
private String email;
+) getter, setter 추가
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) {
}
}
1.1 구조 요약
1) 클라이언트 (LoginHandler)
2) Account와 AccountService
1.2 문제점
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();
}
}
AccountUserDetails의 역할
1) 클라이언트가 기대하는 인터페이스를 구현:
UserDetails 인터페이스를 구현하여 클라이언트(LoginHandler)에서 사용할 수 있도록 변환한다.
2) Account와 UserDetails의 간격을 메움:
Account의 필드(name, password, email)를 UserDetails의 필드(username, password)로 매핑한다.
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));
}
}
AccountUserDetailsService의 역할
1) 클라이언트가 기대하는 인터페이스를 구현:
UserDetailsService 인터페이스를 구현하여 클라이언트(LoginHandler)에서 사용할 수 있도록 변환한다.
2) AccountService와 UserDetailsService의 간격을 메움:
AccountService의 findAccountByUsername 메서드를 호출하여 Account 객체를 가져온 뒤, 이를 AccountUserDetails로 변환하여 반환한다.
3) 기존 AccountService와 클라이언트의 연결:
기존의 AccountService를 수정하지 않고, 클라이언트가 기대하는 방식으로 호출할 수 있게 해준다.
결과

장점
기존 코드 재사용:
책임 분리:
유연성:
단점
복잡성 증가:
추가적인 관리:
1. 자바에서의 사용 사례
java.util.Arrays#asList(T…):
List로 변환하는 어댑터 메서드.String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array); // 배열을 리스트로 변환java.util.Collections#list(Enumeration) 및 Collections#enumeration():
Enumeration을 List로 변환하거나, List를 Enumeration으로 변환하는 메서드.Vector<String> vector = new Vector<>();
vector.add("A");
Enumeration<String> enumeration = Collections.enumeration(vector); // List -> Enumeration
List<String> list = Collections.list(enumeration); // Enumeration -> Listjava.io.InputStreamReader(InputStream):
InputStream을 Reader로 변환하는 어댑터 클래스.InputStream inputStream = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(inputStream); // InputStream -> Readerjava.io.OutputStreamWriter(OutputStream):
OutputStream을 Writer로 변환하는 어댑터 클래스.OutputStream outputStream = new FileOutputStream("file.txt");
Writer writer = new OutputStreamWriter(outputStream); // OutputStream -> Writer2. 스프링에서의 사용 사례
HandlerAdapter:
HandlerMethod를 실행하는 RequestMappingHandlerAdapter.SimpleControllerHandlerAdapter는 이전 버전의 Controller 인터페이스를 처리.동작 예:
DispatcherServlet에 요청을 전달하면, HandlerAdapter가 해당 요청을 처리 가능한 핸들러로 변환한다.ViewResolver와 연계:
ViewResolver는 요청을 HTML, JSON, XML 등 다양한 형식으로 변환하여 응답하는 역할을 한다.View를 생성.| 항목 | 내용 |
|---|---|
| 정의 | 기존 코드를 클라이언트가 사용하는 인터페이스로 변환하는 패턴. |
| 주요 목적 | 기존 코드를 수정하지 않고, 원하는 인터페이스를 구현하여 재사용 가능. |
| 장점 | 기존 코드 재사용, 책임 분리, 유연성 증가. |
| 단점 | 클래스 수 증가로 인한 복잡도 증가. |
| 자바 사용 사례 | Arrays.asList(), Collections.enumeration(), InputStreamReader, OutputStreamWriter. |
| 스프링 사용 사례 | HandlerAdapter(스프링 MVC 핸들러 변환), ViewResolver와의 연계. |

Cilent는 Implementation 부분을 직접적으로 접근하지 않는다는게 장점!
public interface Skin {
String getName();
}
public interface Champion extends Skin {
void move();
void skillQ();
void skillW();
void skillE();
void skillR();
}
public class KDA아리 implements Champion {
@Override
public void move() {
System.out.println("KDA 아리 move");
}
@Override
public void skillQ() {
System.out.println("KDA 아리 Q");
}
@Override
public void skillW() {
System.out.println("KDA 아리 W");
}
@Override
public void skillE() {
System.out.println("KDA 아리 E");
}
@Override
public void skillR() {
System.out.println("KDA 아리 R");
}
@Override
public String getName() {
return null;
}
}
public class 정복자아리 implements Champion {
@Override
public void move() {
System.out.println("정복자 아리 move");
}
@Override
public void skillQ() {
System.out.println("정복자 아리 Q");
}
@Override
public void skillW() {
System.out.println("정복자 아리 W");
}
@Override
public void skillE() {
System.out.println("정복자 아리 E");
}
@Override
public void skillR() {
System.out.println("정복자 아리 R");
}
@Override
public String getName() {
return null;
}
}
..KDA아칼리, KDA카이사...
1) 클래스 폭발 문제
새로운 챔피언(아리, 아칼리, 카이사 등)과 새로운 스킨(KDA, 정복자 등)이 추가될 때마다 모든 조합에 대해 새로운 클래스를 생성해야 한다.
예: KDA 아리, 정복자 아리, KDA 아칼리, 정복자 아칼리, ...
2) 유지보수 어려움
각 챔피언-스킨 조합에 대한 구현 클래스가 많아지면서, 특정 챔피언이나 스킨에 대한 수정이 어려움.
3) 책임 분리가 불분명
Champion 인터페이스에서 스킨(Skin)과 스킬(챔피언의 동작)이 혼합되어 있음.
챔피언과 스킨은 독립적인 개념인데, 이를 하나의 계층 구조로 묶어버림.
Skin 인터페이스public interface Skin {
String getName();
}
KDA, PoolParty 등이 Skin 인터페이스를 구현.public class KDA implements Skin {
@Override
public String getName() {
return "KDA";
}
}
public class PoolParty implements Skin {
@Override
public String getName() {
return "PoolParty";
}
}
Champion 인터페이스public interface Champion {
void move();
void skillQ();
void skillW();
void skillE();
void skillR();
String getName();
}
DefaultChampion 클래스Skin)을 포함하여, 챔피언의 스킬, 이동 동작과 스킨을 조합.public class DefaultChampion implements Champion {
private Skin skin;
private String name;
public DefaultChampion(Skin skin, String name) {
this.skin = skin;
this.name = name;
}
@Override
public void move() {
System.out.printf("%s %s move\n", skin.getName(), this.name);
}
@Override
public void skillQ() {
System.out.printf("%s %s Q\n", skin.getName(), this.name);
}
@Override
public void skillW() {
System.out.printf("%s %s W\n", skin.getName(), this.name);
}
@Override
public void skillE() {
System.out.printf("%s %s E\n", skin.getName(), this.name);
}
@Override
public void skillR() {
System.out.printf("%s %s R\n", skin.getName(), this.name);
}
@Override
public String getName() {
return this.name;
}
}
아리, 아칼리)은 DefaultChampion을 상속받아 구현.DefaultChampion에서 처리.public class 아리 extends DefaultChampion {
public 아리(Skin skin) {
super(skin, "아리");
}
}
public class 아칼리 extends DefaultChampion {
public 아칼리(Skin skin) {
super(skin, "아칼리");
}
}
public class Main {
public static void main(String[] args) {
// KDA 스킨을 사용하는 아리
Champion kdaAhri = new 아리(new KDA());
kdaAhri.move(); // KDA 아리 move
kdaAhri.skillQ(); // KDA 아리 Q
// PoolParty 스킨을 사용하는 아칼리
Champion poolPartyAkali = new 아칼리(new PoolParty());
poolPartyAkali.move(); // PoolParty 아칼리 move
poolPartyAkali.skillR(); // PoolParty 아칼리 R
}
}
출력 결과:
KDA 아리 move
KDA 아리 Q
PoolParty 아칼리 move
PoolParty 아칼리 R

- 독립적인 확장성:
- 책임 분리:
- 유지보수 용이성:
- 클래스 폭발 문제 해결:
구조적 복잡성 증가:
초기 설계 비용 증가:
JDBC API
구조:
DriverManager (API)Driver (MySQL, Oracle 등 데이터베이스 드라이버)SLF4J (Simple Logging Facade for Java)
Portable Service Abstraction (PSA):
구조:
스프링 JDBC
JdbcTemplate (스프링 추상화)| 항목 | 내용 |
|---|---|
| 정의 | 추상적인 것(Abstraction)과 구체적인 것(Implementation)을 분리하여 독립적으로 확장 가능. |
| 주요 목적 | 추상적인 코드와 구체적인 코드를 연결(Bridge)하여 서로 독립적으로 관리 및 확장. |
| 장점 | 독립적 확장성, 책임 분리, 유지보수성 향상, 클래스 폭발 문제 해결. |
| 단점 | 계층 구조 증가로 인한 복잡도 증가, 초기 설계 비용 상승. |
| 자바 사용 사례 | JDBC API (DriverManager와 Driver), SLF4J (퍼사드와 로깅 구현체). |
| 스프링 사용 사례 | Portable Service Abstraction (트랜잭션, 캐싱, 이메일), 스프링 JDBC (JdbcTemplate). |