구조 (1) - 어댑터(Adapter) 패턴
- 한 클래스의 인터페이스를 사용하고자 하는 다른 인터페이스로 변환
- 클라이언트가 사용하는 인터페이스를 따르지 않는 기존의 코드를 재사용할 수 있게 된다
- 써드파티 라이브러리의 인터페이스를 일반적으로 애플리케이션 코드로 변환하는데 사용
- ex) 220V-110V어댑터(a.k.a 돼지코)
용어
- Client : 클라이언트 코드
- Target Interface : 어댑터가 구현하는 인터페이스 (From)
- Adapter : Client-Adaptee를 연결해주는 역할
- Adaptee : 클라이언트 코드와 호환성 없는 외부 코드 (To)
구현
- Target Interface : 클라이언트가 사용하는 인터페이스이자 Adapter가 연결할 인터페이스 (어댑터 작동 목표)
public interface Electronic110V {
void powerOn();
}
public class HairDryer implements Electronic110V {
@Override
public void powerOn() {
System.out.println("HairDryer On");
}
}
- Adaptee : 클라이언트 코드와 연관없는 외부 코드, 어댑터에 의해 변환 당할 자료형
public interface Electronic220V {
void connect();
}
public class Cleaner implements Electronic220V {
@Override
public void connect() {
System.out.println("Cleaner On");
}
}
- Adapter : 변환 결과(어댑터 작동 목표)의 인터페이스를 구현
-
Electronic220V
인터페이스를 쓰는 객체를Electronic110V
로 변환 (220V -> 110V) - SocketAdapter : 220V 전자기기를 110V 콘센트에 꽂을 수 있도록 하는 어댑터
-
public class SocketAdapter implements Electronic110V {
Electronic220V electronic220V; // Adaptee
public SocketAdapter(Electronic220V electronic220V) {
this.electronic220V = electronic220V;
}
@Override
public void powerOn() {
System.out.print("Using Adapter : ");
electronic220V.connect();
}
}
public class Main {
public static void main(String[] args) {
// HairDryer (110V)
Electronic110V hairDryer = new HairDryer();
hairDryer.powerOn(); // HairDryer On
// Cleaner (220V)
Electronic220V cleaner = new Cleaner();
cleaner.connect(); // Cleaner On
// Adapter (220V -> 110V)
SocketAdapter adapter = new SocketAdapter(cleaner);
adapter.powerOn(); // Using Adapter : Cleaner On
}
}
ex) Spring Security
- Spring Security는 보안 기능을 갖고 있는 스프링 서브 프레임워크이다. 여기에는 로그인 과정에서 사용하기 위한
UserDetails
,UserDetailsService
가 인터페이스가 만들어져 있다 -
어플리케이션에서 사용하는 객체를 Spring Security에 적용하기 위해서는 이에 맞게 변환해서 사용해야 한다 (i.g. username 대신 email 등)
- Target Inferface
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
- Adaptee
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
}
- Adapter
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
...
}
public class UserService implements UserDetailsService {
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return new CustomUserDetails(user);
}
}
장점
- 클라이언트가 사용하는 인터페이스를 따르지 않는 기존의 코드를 재사용할 수 있게 된다
단점
- 새 클래스가 생겨 구조가 복잡해진다