개발블로그

[디자인패턴] 데코레이터 패턴 본문

STUDY/CS

[디자인패턴] 데코레이터 패턴

devmel 2026. 9. 4. 14:56
Contents 접기
 

개념

 

필요성

프로그램을 만들다 보면 기존 기능은 유지하면서, 그 앞뒤로 부가 기능을 추가해야 하는 경우가 있다.

 

EX] 

결제 기능

payment.pay(10000)

처음에는 결제만 처리하면 충분할 수 있다. 

하지만 시간이 지나면 결제 전후로 여러 기능이 추가될 수 있다. 

ex) 로깅, 권한 확인, 캐싱, 알림

 

기능들이 기존 결제 클래스 안에 계속 추가되면, 클래스의 책임이 점점 커짐.

class PaymentService:
    def pay(self, amount):
        check_permission()
        write_log("결제 시작")

        result = process_payment(amount)

        send_notification()
        write_log("결제 완료")

        return result

 

문제는 결제 기능 자체와 부가 기능이 강하게 묶인다.

나중에 로깅만 제거하거나, 알림만 다른 상황에서 추가하고 싶을 때 유연하게 조합하기 어려움.

 

" 기존 객체를 직접 수정하지 않고, 기능을 덧붙일 수 없을까?"

 

 

의미

기존 객체를 감싸는 방식으로 부가 기능을 추가하는 패턴

기존 객체의 코드는 그대로 두고, 그 객체를 감싸는 데코레이터 객체가 앞뒤로 추가 동작을 수행

구분 역할 예시
Component 공통으로 사용할 기능의 형태 Payment
Concrete Component 기본 기능을 수행하는 실제 객체 BasicPayment
Decorator 기존 객체를 감싸고 부가 기능을 추가하는 객체 LoggingPayment, PermissionPayment, NotificationPayment

 

핵심은 기존 객체와 데코레이터가 같은 형태의 메서드를 제공함

그래서 사용하는 쪽에서는 기본 객체인지, 데코레이터로 감싼 객체인지 같은 방식으로 사용할 수 있다.

 

기존 객체를 내부에 가지고 있다가, 필요할 때 기존 객체의 기능을 호출함.

그리고 호출 전후에 자신의 부가 기능을 추가함.

 

 

 


 

예시

 

EX]

결제 기능을 담당하는 BasicPayment

class BasicPayment:
    def pay(self, amount):
        return f"{amount}원 결제 완료"

 

여러 기능이 추가될 때, 가장 간단한 방법은 기존 클래스 안에 부가 기능을 직접 넣는 것

class BasicPayment:
    def pay(self, amount):
        check_permission()
        write_log("결제 시작")

        result = f"{amount}원 결제 완료"

        send_notification()
        write_log("결제 완료")

        return result

 

 


문제 설명
책임 혼합 결제 처리, 권한 확인, 로깅, 알림이 한곳에 섞인다.
조합 어려움 어떤 상황에서는 로깅만, 다른 상황에서는 알림만 적용하기 어렵다.
수정 범위 증가 부가 기능이 바뀔 때 결제 클래스도 함께 수정된다.
클래스 증가 가능성 상속으로 조합하면 경우의 수만큼 클래스가 늘어날 수 있다.

 

기본 기능과 부가 기능을 분리하고, 필요한 기능만 조합할 수 있는 구조가 필요함. 

=> 데코레이터로 기능 감싸기

 

결제 객체가 공통으로 제공할 동작을 정한다

class Payment:
    def pay(self, amount):
        pass

 

기본 결제 기능은 BasicPayment가 담당

class BasicPayment(Payment):
    def pay(self, amount):
        return f"{amount}원 결제 완료"

 

부가 기능은 데코레이터로 분리

class LoggingPayment(Payment):
    def __init__(self, payment):
        self.payment = payment

    def pay(self, amount):
        print("결제 시작 로그")
        result = self.payment.pay(amount)
        print("결제 완료 로그")
        return result

내부에 다른 Payment 객체를 가지고 있음.

그리고 pay()를 호출할 때 기존 객체의 pay()를 실행하면서 앞뒤로 로그를 추가

 

권한 확인도 별도의 데커레이터로 만들 수 있음

class PermissionPayment(Payment):
    def __init__(self, payment):
        self.payment = payment

    def pay(self, amount):
        print("권한 확인")
        return self.payment.pay(amount)

 

필요한 기능을 감싸서 조합할 수 있다.

payment = BasicPayment()
payment = LoggingPayment(payment)
payment = PermissionPayment(payment)

payment.pay(10000)

 

실행흐름)

PermissionPayment.pay()
→ 권한 확인
→ 내부에 감싼 LoggingPayment.pay() 호출
→ 결제 시작 로그
→ 내부에 감싼 BasicPayment.pay() 호출
→ 실제 결제 실행
→ 결제 완료 로그

 

데코레이터들의 pay()는 같은 결제를 여러 번 실행하는 것이 아니라, 요청을 안쪽 객체로 전달하면서 앞뒤에 부가 기능을 추가하는 역할을 한다.

 

 


 

장점

장점

장점 설명
기존 코드 변경 최소화 기본 기능을 담당하는 클래스를 직접 수정하지 않고 기능을 추가할 수 있다.
기능 조합 유연성 필요한 부가 기능을 원하는 순서로 조합할 수 있다.
책임 분리 기본 기능과 부가 기능을 각각 다른 객체로 나눌 수 있다.
상속보다 유연한 확장 기능 조합마다 새로운 자식 클래스를 만들 필요가 줄어든다.
실행 시점 조합 가능 어떤 데코레이터를 적용할지 실행 중에 선택할 수 있다.

 

 

효과적인 상황

상황 이유
기존 코드를 직접 수정하기 어려운 경우 기본 객체를 감싸서 기능을 추가할 수 있다.
부가 기능 조합이 여러 가지인 경우 상속보다 유연하게 조합할 수 있다.
기능을 실행 시점에 선택해야 하는 경우 필요한 데코레이터를 조건에 따라 적용할 수 있다.
기본 기능과 부가 기능을 분리하고 싶은 경우 각 기능의 책임을 나눌 수 있다.

 

 

 


 

주의점

단점

주의점 설명
흐름 추적 어려움 여러 객체가 겹쳐 있으면 실제 실행 순서를 파악하기 어려울 수 있다.
순서 의존성 권한 확인, 로깅, 알림처럼 실행 순서가 중요한 기능은 조합 순서를 신경 써야 한다.
디버깅 어려움 문제가 발생했을 때 어느 데코레이터에서 발생했는지 추적해야 한다.
과한 구조 가능성 단순한 기능 추가에는 데코레이터보다 직접 구현이 더 명확할 수 있다.
책임 혼합 위험 데코레이터가 부가 기능을 넘어 핵심 비즈니스 로직까지 담당하면 구조가 흐려진다.

 

 

주의점

질문 의미
기존 객체를 직접 수정하지 않아야 하는 이유가 있는가? 데코레이터를 둘 필요가 있는지 확인한다.
부가 기능을 여러 방식으로 조합해야 하는가? 조합의 이점이 있는지 판단한다.
실행 순서가 중요한가? 데코레이터를 감싸는 순서를 명확히 해야 한다.
데코레이터가 너무 많이 겹치지 않는가? 구조가 과하게 복잡해지는지 확인한다.
부가 기능과 핵심 기능의 책임이 분리되어 있는가? 데코레이터가 핵심 로직을 침범하지 않게 한다.

'STUDY > CS' 카테고리의 다른 글

[디자인패턴] 싱글톤 패턴  (0) 2026.09.04
[디자인패턴] 어댑터 패턴  (0) 2026.09.04
[디자인패턴] 옵저버 패턴  (0) 2026.09.04
[디자인패턴] 팩토리 패턴  (1) 2026.09.04
[디자인 패턴] 전략패턴  (0) 2026.09.04