래핑 패턴 4형제, 왜 이렇게 헷갈릴까요
디자인 패턴 공부하다 보면 꼭 벽에 부딪히는 지점이 있어요.
어댑터, 퍼사드, 프록시, 데코레이터. 이 네 개가 자꾸 섞여서 헷갈리기 쉽습니다.
“다 그냥 다른 객체를 감싸는 거 아냐?” 싶을 만한데요. 실제로 넷 다 안에 다른 객체를 품고 그 앞에 서 있는 구조라 겉모습이 비슷합니다.
그래서 이름은 외웠는데 막상 코드에서 뭐가 뭔지 구분이 안 되는 경우가 많죠.
핵심부터 말하면 이렇습니다.
네 패턴 모두 객체를 감싸지만, 감싸는 “목적”이 다릅니다.
어댑터는 인터페이스를 바꾸고, 퍼사드는 복잡함을 숨기고, 프록시는 접근을 통제하고, 데코레이터는 기능을 더합니다.
이 한 줄만 손에 쥐고 있으면 나머지는 따라옵니다. 오늘 이 글에서 네 형제를 확실히 갈라놓고 가실 수 있게 정리해볼게요.
한눈에 보는 4형제 구분표
말로 풀기 전에 표로 먼저 볼게요. 저는 이 표를 책상 앞에 붙여두고 헷갈릴 때마다 봤어요.
| 패턴 | 목적 | 인터페이스 | 감싸는 대상 |
|---|---|---|---|
| 어댑터(Adapter) | 안 맞는 인터페이스를 맞춰줌 | 바뀜 | 보통 1개 |
| 퍼사드(Facade) | 복잡한 여러 서브시스템을 단순 창구로 | 새로 만듦 | 여러 개 |
| 프록시(Proxy) | 원본 접근을 통제·지연·캐싱 | 똑같음 | 1개 |
| 데코레이터(Decorator) | 원본에 기능을 덧붙임 | 똑같음 | 1개 |
여기서 제일 중요한 판별 기준은 “인터페이스가 그대로냐”입니다.
프록시와 데코레이터는 원본과 인터페이스가 똑같아요. 그래서 쓰는 쪽은 감싸진 걸 몰라도 됩니다.
반대로 어댑터는 인터페이스를 일부러 바꾸고, 퍼사드는 아예 새 창구를 만들어요.
어댑터 vs 데코레이터, 어떻게 다른가요
가장 많이 헷갈리는 조합이 이 둘입니다. 코드로 보면 감이 확 와요.
어댑터는 “안 맞는 걸 맞춰주는” 콘센트 변환 플러그예요. 220V 기기를 110V 소켓에 꽂는 그 어댑터 맞습니다.
// 기존 라이브러리는 legacyPrint(text:)만 있는데
// 우리 코드는 draw()를 기대할 때
protocol Renderer { func draw() }
struct LegacyLabel { func legacyPrint(_ t: String) { print(t) } }
struct LabelAdapter: Renderer {
let legacy: LegacyLabel
func draw() { legacy.legacyPrint("변환된 호출") }
}
LabelAdapter(legacy: LegacyLabel()).draw()
// 출력: 변환된 호출
인터페이스가 legacyPrint에서 draw로 바뀐 게 핵심이에요.
데코레이터는 인터페이스는 그대로 두고 기능만 얹습니다. 커피에 샷 추가하듯이요.
protocol Coffee { func cost() -> Int }
struct Americano: Coffee { func cost() -> Int { 4000 } }
struct ShotDecorator: Coffee {
let base: Coffee
func cost() -> Int { base.cost() + 500 }
}
ShotDecorator(base: Americano()).cost()
// 출력: 4500
둘 다 base를 품고 있지만 어댑터는 이름을 바꾸고 데코레이터는 값을 키웁니다. 목적이 완전히 다르죠.
프록시 vs 퍼사드는요
프록시는 인터페이스가 원본과 똑같아요. 대신 중간에서 통제를 합니다.
이미지 로딩을 예로 들면, 진짜 이미지를 바로 안 불러오고 필요할 때까지 미뤄요. 이걸 지연 로딩이라고 하죠.
캐싱, 권한 검사, 로깅도 프록시가 자주 맡는 일이에요. 쓰는 쪽은 원본을 부르는 줄 알지만 실제론 대리인을 부르고 있는 겁니다.
퍼사드는 결이 좀 달라요. 얘는 하나를 감싸는 게 아니라 여러 개를 뒤에 숨깁니다.
홈시어터를 예로 들면, 프로젝터 켜고 스피커 켜고 조명 낮추고 재생하는 과정을 watchMovie() 하나로 묶어주는 거예요. 복잡한 내부를 몰라도 버튼 하나면 되게요.
정리하면 프록시는 “같은 문 앞의 문지기”고, 퍼사드는 “여러 문을 대신 여는 안내데스크”입니다.
언제 쓰고 언제 피해야 할까
패턴은 도구라 상황에 맞게 꺼내 써야 해요. 억지로 끼워 맞추면 오히려 코드가 복잡해집니다.
| 상황 | 판단 |
|---|---|
| 외부 라이브러리 인터페이스가 안 맞음 | 어댑터 |
| 복잡한 여러 모듈을 쉬운 창구로 열고 싶음 | 퍼사드 |
| 접근 제어·지연 로딩·캐싱이 필요함 | 프록시 |
| 기능을 조합해서 유연하게 붙이고 싶음 | 데코레이터 |
피해야 할 때도 분명해요.
- 감쌀 이유가 없는데 “패턴 써봤다”는 이유로 넣으면 계층만 늘어요
- 데코레이터를 너무 겹치면 디버깅할 때 어디서 값이 바뀌는지 추적이 지옥이 됩니다
- 퍼사드가 너무 많은 걸 떠안으면 그 자체가 거대한 덩어리가 돼요
저는 “지금 감싸는 목적이 위 네 개 중 뭐지?“를 스스로 물어보고, 답이 안 나오면 패턴을 빼는 편이에요.
면접에서는 이렇게 물어봅니다
Q. 프록시와 데코레이터는 둘 다 같은 인터페이스로 객체를 감싸는데 뭐가 다른가요?
A. 목적이 다릅니다. 프록시는 원본 접근을 통제하거나 지연·캐싱하는 게 목적이고 원본의 기능 자체는 그대로 둡니다. 데코레이터는 원본의 인터페이스를 유지하면서 기능을 덧붙이는 게 목적이에요. 프록시는 “부를지 말지”를 결정하고 데코레이터는 “부른 다음 뭘 더할지”를 결정한다고 보면 됩니다.
Q. 어댑터와 퍼사드의 차이는요?
A. 어댑터는 보통 하나의 대상을 감싸서 안 맞는 인터페이스를 맞춰주는 거고 퍼사드는 여러 서브시스템을 감싸서 단순한 새 창구를 제공하는 겁니다. 어댑터는 인터페이스를 “번역”하고 퍼사드는 인터페이스를 “요약”한다고 이해하면 편해요.
마무리하며
네 형제를 가르는 건 결국 “왜 감싸는가” 하나예요. 인터페이스를 바꾸려는지, 숨기려는지, 통제하려는지, 더하려는지.
이 질문 하나면 코드 앞에서 더는 헷갈리지 않으실 거예요. 오늘 배운 표 한 장, 꼭 저장해두고 필요할 때 꺼내 보시길 응원할게요.
