10. 퍼사드 패턴 (Facade)
복잡한 내부 시스템을 숨기고 단순화된 단일 창구를 제공하는 퍼사드 패턴을 알아봅니다.
한 줄 요약
여러 객체를 정해진 순서로 다뤄야 하는 복잡한 일을 창구 하나 뒤에 모아 두고, 쓰는 쪽은 그 창구에 한 번만 부탁하게 하는 패턴이에요. 퍼사드(Facade)는 건물의 “정면”이라는 뜻이에요.
문제: 쓰는 곳마다 복잡한 순서를 외워야 하는 코드
온라인 가게의 주문 프로그램을 만든다고 해 볼게요. 주문 하나를 받으려면 네 시스템을 순서대로 다뤄야 해요.
- 재고가 충분한지 확인하고, 있으면 빼요.
- 결제기로 돈을 받아요. (하나에 10000원)
- 배송을 접수해요.
- 손님에게 문자를 보내요.
주문은 앱에서도, 가게 앞 키오스크에서도 들어와요. 가장 먼저 떠오르는 방법은 주문을 받는 곳마다 네 시스템을 직접 다루는 거예요.
(참고: 주문 하나에 필요한 시스템들이에요. 저마다 할 줄 아는 일이 달라요)
[재고]를 설계하자:
'남은수'를 [숫자]인 2로 정하자
[논리]를 돌려주는 <있는지>를 만들자 ([숫자]인 '개수'):
('나'의 '남은수' >= '개수')를 돌려주자
<빼기>를 만들자 ([숫자]인 '개수'):
'나'의 '남은수'를 ('나'의 '남은수' - '개수')로 정하자
[결제기]를 설계하자:
<결제하기>를 만들자 ([숫자]인 '원'):
틀"[결제] {'원'}원"을 출력하자
[배송]을 설계하자:
<접수하기>를 만들자 ([문자열]인 '주소'):
틀"[배송] {'주소'}(으)로 접수"를 출력하자
[문자]를 설계하자:
<보내기>를 만들자 ([문자열]인 '내용'):
틀"[문자] {'내용'}"을 출력하자
(참고: 주문하는 곳마다 네 시스템을 직접, 순서대로 다뤄요)
<앱주문>을 만들자 ('재고', '결제기', '배송', '문자', [숫자]인 '개수', [문자열]인 '주소'):
만약 ('재고'의 <있는지>('개수')가 거짓과 같다) 라면:
"[앱] 재고가 모자라요."를 출력하자
돌려주자
'재고'의 <빼기>('개수')를 실행하자
'결제기'의 <결제하기>('개수' * 10000)를 실행하자
'배송'의 <접수하기>('주소')를 실행하자
'문자'의 <보내기>("주문이 접수됐어요!")를 실행하자
<키오스크주문>을 만들자 ('재고', '결제기', '배송', '문자', [숫자]인 '개수', [문자열]인 '주소'):
(참고: 재고 확인을 깜빡했어요)
'재고'의 <빼기>('개수')를 실행하자
'결제기'의 <결제하기>('개수' * 10000)를 실행하자
'배송'의 <접수하기>('주소')를 실행하자
'문자'의 <보내기>("주문이 접수됐어요!")를 실행하자
'가게재고'를 새로운 [재고]()로 정하자
'가게결제기'를 새로운 [결제기]()로 정하자
'가게배송'을 새로운 [배송]()으로 정하자
'가게문자'를 새로운 [문자]()로 정하자
<앱주문>('가게재고', '가게결제기', '가게배송', '가게문자', 2, "서울시 하리구")를 실행하자
<키오스크주문>('가게재고', '가게결제기', '가게배송', '가게문자', 1, "부산시 카나데구")를 실행하자
틀"남은 재고: {'가게재고'의 '남은수'}"를 출력하자
실행 결과
[결제] 20000원
[배송] 서울시 하리구(으)로 접수
[문자] 주문이 접수됐어요!
[결제] 10000원
[배송] 부산시 카나데구(으)로 접수
[문자] 주문이 접수됐어요!
남은 재고: -1
재고가 -1이 됐어요. 없는 물건을 팔고 돈까지 받았어요.
- 같은 순서가 주문하는 곳마다 복사돼요.
<앱주문>과<키오스크주문>이 네 시스템을 똑같은 순서로 다뤄요. 전화 주문이 생기면 또 복사해야 해요. - 한 곳에서 빠뜨리면 버그가 돼요. 키오스크 쪽은 재고 확인을 깜빡했어요. 에러는 안 났지만 재고가 마이너스가 됐어요.
- 주문하는 곳이 너무 많이 알아요.
<키오스크주문>은 “주문을 받는다”만 알면 되는데, 시스템 네 개의 이름과 메서드, 순서, 가격 계산까지 다 알아야 해요. 인자만 여섯 개예요. - 순서가 바뀌면 모든 곳을 고쳐야 해요. “문자를 결제 전에 보내 주세요”라는 요청이 오면, 주문하는 곳을 하나하나 찾아 고쳐야 해요.
해결책: 복잡한 일 앞에 창구 하나 세우기
퍼사드 패턴은 복잡한 시스템들을 고치지 않고, 그 앞에 쉬운 창구를 하나 세워요.
- 재고, 결제기, 배송, 문자는 그대로 둬요. 저마다 자기 일만 잘하면 돼요.
- 창구를 만들어요. 창구는 네 시스템을 품고 있어요.
- 창구에
<주문하기>를 하나 만들고, 그 안에서 올바른 순서를 한 번만 적어요. 재고 확인도 여기 있어요. - 주문하는 곳은 이제 창구에
<주문하기>한 번만 부탁해요. 네 시스템은 몰라도 돼요.
비유: 호텔 프런트
호텔에서 “내일 아침 7시에 깨워 주시고, 택시 한 대 불러 주세요”라고 프런트에 말하면 끝이에요. 프런트가 알람 담당, 택시 회사, 청소 담당에게 알아서 연락해요. 손님은 그 사람들의 전화번호도, 부탁하는 순서도 몰라도 돼요. 프런트가 바로 “퍼사드”예요. 그렇다고 직원들이 사라진 건 아니에요. 필요하면 손님이 청소 담당에게 직접 부탁할 수도 있어요.
누가 무엇을 하나요?
| 역할 | 이 예제에서 | 하는 일 |
|---|---|---|
| 복잡한 시스템들 | [재고], [결제기], [배송], [문자] |
저마다 자기 일을 해요. 퍼사드를 몰라요 |
| 퍼사드(창구) | [주문창구] |
시스템들을 품고, 올바른 순서로 다뤄요 |
| 쓰는 곳 | <앱주문>, <키오스크주문> |
창구에 한 번만 부탁해요 |
하리로 만들기
같은 가게를 퍼사드 패턴으로 다시 만들어 볼게요. 네 시스템의 코드는 한 글자도 바뀌지 않았어요.
(참고: 주문 하나에 필요한 시스템들이에요. 저마다 할 줄 아는 일이 달라요)
[재고]를 설계하자:
'남은수'를 [숫자]인 2로 정하자
[논리]를 돌려주는 <있는지>를 만들자 ([숫자]인 '개수'):
('나'의 '남은수' >= '개수')를 돌려주자
<빼기>를 만들자 ([숫자]인 '개수'):
'나'의 '남은수'를 ('나'의 '남은수' - '개수')로 정하자
[결제기]를 설계하자:
<결제하기>를 만들자 ([숫자]인 '원'):
틀"[결제] {'원'}원"을 출력하자
[배송]을 설계하자:
<접수하기>를 만들자 ([문자열]인 '주소'):
틀"[배송] {'주소'}(으)로 접수"를 출력하자
[문자]를 설계하자:
<보내기>를 만들자 ([문자열]인 '내용'):
틀"[문자] {'내용'}"을 출력하자
(참고: 네 시스템을 품고, 주문 한 번에 필요한 순서를 한곳에서 지키는 창구예요)
[주문창구]를 설계하자:
'재고'를 [재고]인 새로운 [재고]()로 정하여 숨기자
'결제기'를 [결제기]인 새로운 [결제기]()로 정하여 숨기자
'배송'을 [배송]인 새로운 [배송]()으로 정하여 숨기자
'문자'를 [문자]인 새로운 [문자]()로 정하여 숨기자
[논리]를 돌려주는 <주문하기>를 만들자 ([숫자]인 '개수', [문자열]인 '주소'):
만약 ('나'의 '재고'의 <있는지>('개수')가 거짓과 같다) 라면:
"재고가 모자라요."를 출력하자
거짓을 돌려주자
'나'의 '재고'의 <빼기>('개수')를 실행하자
'나'의 '결제기'의 <결제하기>('개수' * 10000)를 실행하자
'나'의 '배송'의 <접수하기>('주소')를 실행하자
'나'의 '문자'의 <보내기>("주문이 접수됐어요!")를 실행하자
참을 돌려주자
[숫자]를 돌려주는 <남은재고>를 만들자 ():
'나'의 '재고'의 '남은수'를 돌려주자
(참고: 주문하는 곳은 창구 하나만 알아요)
<앱주문>을 만들자 ([주문창구]인 '창구', [숫자]인 '개수', [문자열]인 '주소'):
'창구'의 <주문하기>('개수', '주소')를 실행하자
<키오스크주문>을 만들자 ([주문창구]인 '창구', [숫자]인 '개수', [문자열]인 '주소'):
'창구'의 <주문하기>('개수', '주소')를 실행하자
'가게'를 새로운 [주문창구]()로 정하자
<앱주문>('가게', 2, "서울시 하리구")를 실행하자
<키오스크주문>('가게', 1, "부산시 카나데구")를 실행하자
틀"남은 재고: {'가게'의 <남은재고>()}"를 출력하자
실행 결과
[결제] 20000원
[배송] 서울시 하리구(으)로 접수
[문자] 주문이 접수됐어요!
재고가 모자라요.
남은 재고: 0
이번에는 키오스크 주문이 “재고가 모자라요”로 막혔고, 재고는 0에서 멈췄어요. 키오스크 쪽에서 재고 확인을 빠뜨릴 수가 없어요. 확인하는 코드가 창구 안에 한 번만 있기 때문이에요.
하나씩 다시 보기
- 시스템들은 그대로.
[재고],[결제기],[배송],[문자]는 바뀌지 않았어요. 퍼사드가 있다는 것도 몰라요. - 순서는 창구 한 곳에. 재고 확인 → 재고 빼기 → 결제 → 배송 → 문자 순서가
<주문하기>안에만 있어요. “문자를 결제 전에” 같은 요청이 오면 여기 두 줄만 바꾸면 돼요. - 주문하는 곳은 한 줄.
<앱주문>과<키오스크주문>은 창구에<주문하기>만 부탁해요. 인자도 여섯 개에서 세 개로 줄었어요. - 결과도 알려 줘요.
<주문하기>는 주문이 됐는지참/거짓으로 돌려줘요. 주문하는 곳은 필요하면 이 값만 보고 화면에 알리면 돼요.
창구가 시스템을 완전히 가리나요?
꼭 그렇지는 않아요. 이 예제에서는 시스템들을숨기자로 넣어서 창구를 통해서만 쓰게 했지만, 퍼사드의 핵심은 “자주 하는 복잡한 일을 쉽게” 만드는 거예요. 드물게 필요한 세세한 일(재고만 따로 채우기 같은)은 시스템을 직접 쓰게 열어 두는 경우도 많아요. 이 예제의<남은재고>처럼 필요한 것만 창구에 하나씩 더해도 되고요.
새로 주문받는 곳을 더해 보면
“전화 주문”이 생겼다고 해 볼게요.
- 앞의 방식: 네 시스템을 다루는 순서를 또 한 벌 복사해야 해요. 이번에도 무언가를 빠뜨릴 수 있어요.
- 퍼사드 패턴:
<전화주문>을 만들고 그 안에서'창구'의 <주문하기>(...)를 한 번 부르면 끝이에요. 순서를 빠뜨릴 방법이 없어요.
직접 해 보기
- 위 코드에
<전화주문>을 더해서, 재고가 0인 지금 상태에서 한 개를 주문해 보세요. “재고가 모자라요.“가 나와야 해요.[주문창구]에<입고하기>([숫자]인 '개수')를 더해서 재고를 채울 수 있게 만들어 보세요.[재고]에는 이미<빼기>가 있으니, 비슷하게<채우기>를 만들면 돼요. 채운 뒤에 다시 주문해 보세요.
언제 쓰면 좋을까요?
- 한 가지 일을 하려면 여러 객체를 정해진 순서로 다뤄야 할 때
- 그 순서가 여러 곳에 복사되기 시작할 때
- 쓰는 쪽이 복잡한 내부를 몰라도 되게 하고 싶을 때 (처음 쓰는 사람, 다른 팀, 패키지를 쓰는 사람)
- 복잡한 시스템을 바꾸거나 갈아 끼울 때, 바뀌는 곳을 창구 한 곳으로 모으고 싶을 때
좋은 점과 아쉬운 점
| 좋은 점 | 아쉬운 점 |
|---|---|
| 복잡한 순서가 한곳에만 있어서 빠뜨릴 수 없어요 | 창구가 이것저것 다 맡다 보면 너무 커질 수 있어요 |
| 쓰는 쪽 코드가 짧고 쉬워져요 | 창구에 없는 세세한 일은 결국 시스템을 직접 써야 해요 |
| 시스템이 바뀌어도 창구만 고치면 돼요 | 창구 하나에 모든 길이 모이면, 창구를 고칠 때 신경 쓸 곳이 많아요 |
| 시스템들은 하나도 고치지 않아요 |
너무 이른 패턴은 독이에요.
주문하는 곳이 한 군데뿐이고 단계도 두세 개라면, 그 자리에서 직접 부르는 게 더 읽기 쉬워요. 같은 순서를 두 번째로 복사하려는 순간이, 또는 “어디선가 한 단계를 빠뜨렸다”는 버그가 처음 나온 순간이 창구를 세울 때예요.