![[디자인 패턴] 3. Adapter 패턴](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255B%25EB%2594%2594%25EC%259E%2590%25EC%259D%25B8%2520%25ED%258C%25A8%25ED%2584%25B4%255D%25203.%2520Adapter%2520%25ED%258C%25A8%25ED%2584%25B4%2520%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3Djay0628&w=2048&q=75)

Adapter 패턴
어댑터 패턴의 목적은 외부 요소를 기존 시스템에 연결하는 것이다.
이때 두 가지 케이스가 있다.
- 외부 요소가 아직 만들어지지 않은 경우
- 외부 요소가 만들어져있으나 호환되지 않는 경우
예제 1. 문지기와 동물들
먼저 기존의 문지기와 동물 예시는 2번 케이스이다.
기존 시스템에서
Doorman
은 Animal
타입만 받아 쫓아낼 수 있다. Mouse
, Cat
과 같은 클래스는 Animal
을 상속하고, name
이라는 상태와 이를 반환하는 getName()
메서드를 가지고 있어 Doorman
과 호환된다.package ex03;
public class Mouse extends Animal {
private String name = "쥐";
public String getName() {
return name;
}
}
public class Cat extends Animal {
private String name = "고양이";
public String getName() {
return name;
}
}
public class Doorman {
// 객체의 책임 = 메서드
public void 쫓아내(Animal a) {
System.out.println(a.getName() + " 나가!");
}
}
하지만 외부 요소인
OuterTiger
는 상황이 다르다.Animal
을 상속하지 않기 때문에Doorman
에 직접 전달할 수 없다.
- 내부 상태 필드도
name
이 아닌fullName
, 메서드도getName()
이 아닌getFullName()
을 사용한다.
따라서
Doorman
에서 다음과 같이 사용하는 것은 불가능하다d1.쫓아내(new OuterTiger()); // 컴파일 에러 발생
이 문제를 해결하기 위해 Adapter 패턴을 적용할 수 있다.
해결 방법: TigerAdapter
클래스 생성
TigerAdapter
는 기존 시스템의 기준에 맞춰Animal
을 상속한다.
OuterTiger
는TigerAdapter
의 필드(컴포지션)로 포함시키고, 생성자를 통해 주입받는다.
Animal
의 추상 메서드인getName()
을 재정의하면서, 내부의outerTiger.getFullName()
을 반환하도록 구현한다.
public class TigerAdapter extends Animal {
private OuterTiger outerTiger;
public TigerAdapter(OuterTiger outerTiger) {
this.outerTiger = outerTiger;
}
@Override
public String getName() {
return outerTiger.getFullName();
}
}
이렇게 하면
TigerAdapter
는 Animal
타입이므로 Doorman
에서 문제없이 사용할 수 있고, 내부적으로는 OuterTiger
의 데이터를 활용할 수 있다:
Doorman d1 = new Doorman();
OuterTiger tiger = new OuterTiger();
d1.쫓아내(new TigerAdapter(tiger)); // 정상 작동
+ 어댑터도 추상화시키기
현재는 OuterTiger만 호환가능하도록 만들기 위해 TigerAdapter로 만들었지만 OuterSnake, OuterRabbit 등등 외부 요소가 많아지만 TigerAdapter라고 짓지 않고 이 또한 추상화시켜서 AnimalAdapter라는 상위 어댑터를 통해 해결한다.
public abstract class AnimalAdapter extends Animal {
public abstract String getName(); // Animal과 동일한 추상 메서드
}
// 각각의 외부 요소별 어댑터는 AnimalAdapter를 상속
public class TigerAdapter extends AnimalAdapter {
private OuterTiger tiger;
public TigerAdapter(OuterTiger tiger) {
this.tiger = tiger;
}
@Override
public String getName() {
return tiger.getFullName();
}
}
public class SnakeAdapter extends AnimalAdapter {
private OuterSnake snake;
public SnakeAdapter(OuterSnake snake) {
this.snake = snake;
}
@Override
public String getName() {
return snake.name();
}
}
++ 왜 상속이 아니라 컴포지션이어야 할까?
먼저
OuterTiger
는 외부 요소이므로 내가 직접 만든 코드가 아니기 때문에 직접 수정하거나 상속 구조에 넣을 수 없으므로 Animal
을 상속받을 수 없다. 또한 TigerAdapter
는 Animal 로 동작하면서도 OuterTiger의 기능을 내부적으로 사용한다. 즉, 내부에 포함(composition)해서 위임(delegate)하는 구조이다.따라서 어댑터는 상속이 아니라 컴포지션을 통해 외부 요소를 가지고 있어야 한다.
전체 소스 코드
package ex03;
// 외부 요소
public class OuterTiger {
private String fullName = "호랑이";
public String getFullName() {
return fullName;
}
}
public class TigerAdapter extends Animal {
private OuterTiger outerTiger;
public TigerAdapter(OuterTiger outerTiger) {
this.outerTiger = outerTiger;
}
@Override
public String getName() {
return outerTiger.getFullName();
}
}
public class App {
public static void main(String[] args) {
Mouse m1 = new Mouse();
Doorman d1 = new Doorman();
d1.쫓아내(m1);
Cat c1 = new Cat();
d1.쫓아내(c1);
TigerAdapter t1 = new TigerAdapter(new OuterTiger());
d1.쫓아내(t1);
}
}
예제 2. 소셜 로그인
OAuth 소셜 로그인 기능을 만들때 카카오, 네이버, 구글, 페이스북 등의 여러 소셜 로그인 기능을 사용하게
실제 예시 → 카카오/네이버/구글 등 소셜 로그인시 받는 필드들이 다 다름 → 어댑터 패턴!
package ex99;
import java.util.Scanner;
// FacebookProfile -> uId, uName (이게 추가될때 기존 코드를 손되지 않을 수 있게 하는게 목표)
public class App {
static void mainPage(String username) {
System.out.println("로그인 하신 아이디는 " + username + " 입니다.");
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String provider = sc.nextLine();
Authentication auth = new Authentication();
if (provider.equals("google")) {
GoogleProfile profile = auth.googleLogin();
mainPage(profile.getUsername());
} else if (provider.equals("kakao")) {
KakaoProfile profile = auth.kakaoLogin();
mainPage(profile.getName());
} else {
System.out.println("지원하지 않는 Provider 입니다.");
}
}
}
package ex99;
public class Authentication {
public KakaoProfile kakaoLogin() {
System.out.println("카카오 로그인 완료");
return new KakaoProfile(1, "ssar");
}
public GoogleProfile googleLogin() {
System.out.println("구글 로그인 완료");
return new GoogleProfile(500, "cos");
}
}
package ex99;
public class GoogleProfile {
private int id;
private String username;
public GoogleProfile(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
}
package ex99;
public class KakaoProfile {
private int sub;
private String name;
public KakaoProfile(int sub, String name) {
this.sub = sub;
this.name = name;
}
public int getSub() {
return sub;
}
public String getName() {
return name;
}
}
풀이
package ex99;
// 추상화
public abstract class Authentication {
public abstract UserProfile login();
}
package ex99;
// 추상화
public class UserProfile {
public int getId() {
return 0;
}
public String getUsername() {
return "정원";
}
}
package ex99;
// SRP를 지키기 위해
public class FacebookAuthentication extends Authentication {
public UserProfile login() {
System.out.println("페이스북 로그인 완료");
return new FacebookProfile(999, "love");
}
}
package ex99;
// SRP를 지키기 위해
public class GoogleAuthentication extends Authentication {
public UserProfile login() {
System.out.println("구글 로그인 완료");
return new GoogleProfile(500, "cos");
}
}
package ex99;
// SRP를 지키기 위해
public class KakaoAuthentication extends Authentication {
public UserProfile login() {
System.out.println("카카오 로그인 완료");
return new KakaoProfile(1, "ssar");
}
}
package ex99;
public class FacebookProfile extends UserProfile {
private int uId;
private String uName;
public FacebookProfile(int uId, String uName) {
this.uId = uId;
this.uName = uName;
}
public int getId() {
return uId;
}
public String getUsername() {
return uName;
}
}
package ex99;
public class GoogleProfile extends UserProfile {
private int id;
private String username;
public GoogleProfile(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
}
package ex99;
public class KakaoProfile extends UserProfile {
private int sub;
private String name;
public KakaoProfile(int sub, String name) {
this.sub = sub;
this.name = name;
}
public int getId() {
return sub;
}
public String getUsername() {
return name;
}
}
package ex99;
import java.util.Scanner;
// FacebookProfile -> uId, uName (이게 추가될때 기존 코드를 손되지 않을 수 있게 하는게 목표)
public class App {
static void mainPage(UserProfile profile) {
System.out.println("로그인 하신 아이디는 " + profile.getUsername() + " 입니다.");
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String provider = sc.nextLine();
// 객체 생성 전담 클래스를 만들어서 처리해주면 좋다.
Authentication authentication;
if (provider.equals("google")) {
authentication = new GoogleAuthentication();
} else if (provider.equals("kakao")) {
authentication = new KakaoAuthentication();
} else if (provider.equals("facebook")) {
authentication = new FacebookAuthentication();
} else {
System.out.println("지원하지 않는 Provider 입니다.");
return;
}
UserProfile userProfile = authentication.login();
mainPage(userProfile);
}
}
네이버가 추가된다면?
이미 getMainName과 getId로 만들어진 상태이므로 이걸 건드리지 않고 Adapter를 이용해야한다!
package ex99;
public class NaverProfile extends UserProfile {
private int id;
private String mainName;
public NaverProfile(int id, String mainName) {
this.id = id;
this.mainName = mainName;
}
@Override
public int getId() {
return id;
}
public String getMainName() {
return mainName;
}
}
풀이 관련 필기
1. 책임 분리부터!
-> Authentication의 책임이 너무 많음 GoogleAuthentication, KakaoAuthentication을 만든다.
(패턴 적용 전에 SOLID가 먼저 - 클래스의 책임을 분리시킨다)
카카오로그인, 구글로그인이 단순하지 않고 로직이 다르기 때문에 분리해야하는 것!
interface가 아닌 추상 클래스로 만들어도 됨 (틀린거 X)
kakaoProfile이 이미 만들어진 상태에서 getId return sub 이런식으로 뜯어고치는건 X
-> 처음 만들때는 저렇게 만들어도 되지만
뜯어고치면 이미 다른데에서 쓰고 있으면 다 바뀌어야 되니까 안되고 이때 adapter를 써야됨
구글이 들어오면 구글 객체를 찾아주는 클래스를 만들어주는게 좋다 -> factory
원하는 객체를 찾아서 돌려줌
Authentication a = MyFactroy.getInstance(provider);
이메일 보내는건 doorman의 일이 아님 => proxy 패턴 아님
인터페이스 명세서가 필요
EmailAdapter = mock (mocking된것)
= Mock 객체
나름 고뇌의 흔적..
외부 요소 = OuterTiger
Animal 상속받은 TigerAdapter
1. KakaoProfile, GoogleProfile 등 -> 추상화
추상 클래스 SocialProfile 여기 안에 getName 추상 메서드
2. 그러면 GoogleProfile/ KakaoProfile에서 getName재정의
3. 이제 어댑터가 어떻게 붙어야 하는가...가 문제인데
?를 상속받은 Adapter
외부요소 = ?
new를 도대체 어디서 해야되는건데????????????????????????????????
Share article