[디자인 패턴] 어댑터 패턴(Adapter Pattern)

헨도·2026년 4월 21일

Kotlin

목록 보기
4/4
post-thumbnail

어댑터 패턴이란?

최근 맥북을 구매하면 USB-C 포트만 있습니다.
그런데 내가 가지고 있는 외장하드나 무선 마우스를 연결하려면 USB-A 타입이 필요합니다.

포트 모양이 다르다고 해서 잘 작동하는 외장하드나 무선 마우스를 버리지 않고 우리는 USB-C to A 어댑터를 사서 중간에 끼워 사용합니다.

개발에서도 똑같은 일이 일어나는데요.

우리가 만든 시스템의 규격과 꼭 사용하고 싶은 외부 라이브러리(또는 레거시 코드)의 규격이 맞지 않을 때가 있습니다.
이때 코드를 무작정 뜯어고치는 것보다 서로 호환이 될 수 있도록 중간에 어댑터를 만들어주는 것이 바로 "어댑터 패턴" 입니다.


어댑터 패턴의 4가지 구성 요소

어댑터 패턴의 구조를 이해하기 위해 알아야 할 4가지 요소가 있습니다.

Client (사용자)

  • 어댑터를 통해 외부 기능을 사용하려는 우리의 비즈니스 로직입니다.

Target (타겟 인터페이스)

  • Client가 기대하는 시스템 기본 규격(인터페이스) 입니다.
  • ex. 맥북의 USB-C

Adaptee (어댑티)

  • 규격이 맞지 않아 바로 사용할 수는 없지만, 필요한 기존 코드나 외부 라이브러리
  • ex. USB-A 외장하드

Adapter (어댑터)

  • Adaptee를 Target 규격에 맞게 포장해 주는 클래스
  • ex. USB 젠더

어댑터 패턴 예제 코드 (ex. 스마트홈)

우리 스마트홈 앱은 집 안의 모든 전자기기를 스마트폰으로 켜고 끌 수 있게 도와줍니다.

1. Target (우리의 규격)

  • 우리 스마트홈 앱에 연동되려면 반드시 SmartDevice 규격을 따라야합니다.
  • turnOn / turnOff 기능은 필수입니다.
// Target Interface
interface SmartDevice {
	fun turnOn()
    fun turnOff()
}

2. 규격을 잘 따르는 최신 기기

최근에 산 스마트 조명은 이 규격을 완벽하게 따르고 있어, 앱에 바로 연결하여 사용할 수 있습니다.

class ModernLight: SmartDevice {
	override fun turnOn() {
    	println("스마트 조명이 켜졌습니다.")
    }
    
    override fun turnOff() {
    	println("스마트 조명이 꺼졌습니다.")
    }
}

3. 문제의 구형 기기

그런데 집에 10년 전에 구매한 에어컨이 있습니다.
이 에어컨도 앱으로 제어하고 싶은데 구형 제품이라 turnOn/Off 기능은 없고 powerSupply(Boolean) 이라는 전혀 다른 방식의 메서드를 사용합니다.
게다가 이 코드는 낡아서 함부로 수정할 수 없는 상황입니다.

class LegacyAirConditioner {
	// 켜고 끄는 방식이 우리 시스템과 완전히 다릅니다.
    fun powerSupply(state: Boolean) {
    	if (state) {
        	println("구형 에어컨에 전원이 공급됩니다.")
        } else {
        	println("구형 에어컨 전원이 차단됩니다.")
        }
    }
}

4. 어댑터 만들기

구형 에어컨 코드를 수정할 수 없기에 에어컨을 우리 스마트홈 규격(SmartDevice)에 맞게 감싸주는 어댑터를 생성합니다.

class LegacyDeviceAdapter(
	private val legacyAc: LegacyAirConditioner // 내부에 구형 기기 탑재
) : SmartDevice { // 우리 시스템 규격을 따른다고 선언
	
    // 앱에서 turnOn()을 누르면, 어댑터의 powerSupply 연결해서 실행
    override fun turnOn() {
    	legacyAc.powerSupply(true)
    }
    
    override fun turnOff() {
    	legacyAc.powerSupply(false)
    }
}

5. 사용하기

fun main() {
	// 스마트홈 앱에 등록된 기기 목록
	val myDevices = mutableListOf<SmartDevice>()
    
    // 1. 규격에 맞는 스마트 조명은 바로 추가
    val light = ModernLight()
    myDevices.add(light)
    
    // 2. 구형 에어컨은 규격이 달라서 바로 넣으면 에러 발생
    val oldAc = LegacyAirConditioner()
    // myDevices.add(oldAc) <- 에러 발생
    
    // 3. 구형 에어컨에 어댑터를 사용하여 연결
    val adapter = LegacyDeviceAdapter(oldAc)
    myDevices.add(adapter)
    
    // 앱에서 '전체 켜기' 버튼을 눌렀을 때
    println("---전체 기기 켜기---")
    myDevices.forEach { device ->
    	device.turnOn()
    }
}

6. 출력 결과 확인

--- 전체 기기 켜기 ---
스마트 조명이 켜졌습니다.
구형 에어컨에 전원이 공급됩니다.

언제 사용할까?

외부 라이브러리 연동

  • 남이 만든 코드를 우리 프로젝트 규칙에 맞게 사용할 때

레거시 시스템 확장

  • 오래된 구형 코드를 최신 시스템 규격에 맞춰 재사용해야 할 때

OCP(개방-폐쇄 원칙) 준수

  • 기존 코드를 전혀 수정하지 않고도(Closed), 어댑터 클래스만 추가하여 새로운 기능을 시스템에 연결(Open)하고 싶을 때
profile
Junior Backend Developer

0개의 댓글