[디자인 패턴] 3. Adapter 패턴

김주희's avatar
Jul 23, 2025
[디자인 패턴] 3. Adapter 패턴
notion image

Adapter 패턴

어댑터 패턴의 목적은 외부 요소를 기존 시스템에 연결하는 것이다.
 
이때 두 가지 케이스가 있다.
  1. 외부 요소가 아직 만들어지지 않은 경우
  1. 외부 요소가 만들어져있으나 호환되지 않는 경우
 

예제 1. 문지기와 동물들

먼저 기존의 문지기와 동물 예시는 2번 케이스이다.
 
기존 시스템에서 DoormanAnimal 타입만 받아 쫓아낼 수 있다. 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 클래스 생성

  1. TigerAdapter는 기존 시스템의 기준에 맞춰 Animal상속한다.
  1. OuterTigerTigerAdapter의 필드(컴포지션)로 포함시키고, 생성자를 통해 주입받는다.
  1. 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(); } }
이렇게 하면 TigerAdapterAnimal 타입이므로 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

jay0628