최근 맥북을 구매하면 USB-C 포트만 있습니다.
그런데 내가 가지고 있는 외장하드나 무선 마우스를 연결하려면 USB-A 타입이 필요합니다.
포트 모양이 다르다고 해서 잘 작동하는 외장하드나 무선 마우스를 버리지 않고 우리는 USB-C to A 어댑터를 사서 중간에 끼워 사용합니다.
개발에서도 똑같은 일이 일어나는데요.
우리가 만든 시스템의 규격과 꼭 사용하고 싶은 외부 라이브러리(또는 레거시 코드)의 규격이 맞지 않을 때가 있습니다.
이때 코드를 무작정 뜯어고치는 것보다 서로 호환이 될 수 있도록 중간에 어댑터를 만들어주는 것이 바로 "어댑터 패턴" 입니다.
어댑터 패턴의 구조를 이해하기 위해 알아야 할 4가지 요소가 있습니다.
우리 스마트홈 앱은 집 안의 모든 전자기기를 스마트폰으로 켜고 끌 수 있게 도와줍니다.
SmartDevice 규격을 따라야합니다.turnOn / turnOff 기능은 필수입니다.// Target Interface
interface SmartDevice {
fun turnOn()
fun turnOff()
}
최근에 산 스마트 조명은 이 규격을 완벽하게 따르고 있어, 앱에 바로 연결하여 사용할 수 있습니다.
class ModernLight: SmartDevice {
override fun turnOn() {
println("스마트 조명이 켜졌습니다.")
}
override fun turnOff() {
println("스마트 조명이 꺼졌습니다.")
}
}
그런데 집에 10년 전에 구매한 에어컨이 있습니다.
이 에어컨도 앱으로 제어하고 싶은데 구형 제품이라 turnOn/Off 기능은 없고 powerSupply(Boolean) 이라는 전혀 다른 방식의 메서드를 사용합니다.
게다가 이 코드는 낡아서 함부로 수정할 수 없는 상황입니다.
class LegacyAirConditioner {
// 켜고 끄는 방식이 우리 시스템과 완전히 다릅니다.
fun powerSupply(state: Boolean) {
if (state) {
println("구형 에어컨에 전원이 공급됩니다.")
} else {
println("구형 에어컨 전원이 차단됩니다.")
}
}
}
구형 에어컨 코드를 수정할 수 없기에 에어컨을 우리 스마트홈 규격(SmartDevice)에 맞게 감싸주는 어댑터를 생성합니다.
class LegacyDeviceAdapter(
private val legacyAc: LegacyAirConditioner // 내부에 구형 기기 탑재
) : SmartDevice { // 우리 시스템 규격을 따른다고 선언
// 앱에서 turnOn()을 누르면, 어댑터의 powerSupply 연결해서 실행
override fun turnOn() {
legacyAc.powerSupply(true)
}
override fun turnOff() {
legacyAc.powerSupply(false)
}
}
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()
}
}
--- 전체 기기 켜기 ---
스마트 조명이 켜졌습니다.
구형 에어컨에 전원이 공급됩니다.
Closed), 어댑터 클래스만 추가하여 새로운 기능을 시스템에 연결(Open)하고 싶을 때