프록시 패턴은 원본 객체의 인터페이스를 유지하면서 그 객체에 대한 접근을 중개하고 확장하는 구조적 디자인 패턴
class Payment(ABC): # 추상화/인터페이스 분리원칙(ISP)의 예시
@abstractmethod
def pay(self, amount):
pass
class NaverPayment:
def pay(): ...
class NaverAdapter(Payment):
def __init__(self, naver):
self.naver = naver
def pay(self, amount):
return self.naver.pay(amount)
class TossPayment:
def payment(): ...
**class** TossAdapter**(**Payment**):**
def __init__(self, toss):
self.toss = toss
def pay(self, amount):
return self.toss.payment(amount)
def process_payment(payment_method: Payment, amount):
payment_method.pay(amount)
# --------------
naver_pay = NaverPayment()
naver_adpater = NaverAdapter(naver_pay)
toss_pay = TossPayment()
toss_adapter = TossAdapter(toss_pay)
process_payment(naver_adpater, 10000)
process_payment(toss_adapter, 5000)
어댑터 패턴에서 개선한 코드를 가져와보겠다.
이 코드의 문제점은 다음과 같다.
process_payment
함수가 NaverPayment
와 TossPayment
객체의 메서드를 직접 호출하고 있어 결제 프로세스에 대한 제어가 불가능함.process_payment(naver_adpater, 10000)
process_payment(toss_adapter, 5000)
class TossAdapter(Payment):
def __init__(self, toss):
self.toss = toss
def pay(self, amount):
# 여기서 보안 검사, 권한 확인 실시
return self.toss.payment(amount)
A. 어댑터 패턴의 역할은 인터페이스 간 메서드명만 일치시켜주는 것. 여기서 더 추가하면 SRP에 위반됨def process_payment(payment_method: Payment, amount):
# 여기서 로깅, 성능 측정
payment_method.pay(amount)
결제 방식에 따라 로깅, 성능 측정을 다르게 하고싶지만 모든 결제 방식에 적용되어버리며 SRP가 위반class TossAdapter(Payment):
def __init__(self, toss):
self.toss = toss
def pay(self, amount):
# 여기서 로깅, 성능 측정
return self.toss.payment(amount)
모든 결제 방식에 대해 개별적으로 구현해야 함. 마찬가지로 SRP 위반naver_pay = NaverPayment()
toss_pay = TossPayment()
toss_adapter = TossAdapter(toss_pay)
process_payment(naver_pay, 10000)
process_payment(toss_adapter, 5000)
이 코드를 실행하게 되면 NaverPayment()
, TossPayment()
객체가 즉시 생성되는데 이 객체들은 process_payment
함수 호출 전에 이미 메모리에 존재하게 됨.
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
class NaverPayment(Payment):
def pay(self, amount):...
class NaverAdapter(Payment):
... # 일관성을 위해 어댑터패턴 적용 하였음
class TossPayment(Payment):
def payment(self, amount):...
class TossAdapter(Payment):
def __init__(self, toss):
self.toss = toss
def pay(self, amount):
return self.toss.payment(amount)
class PaymentProxy(Payment):
def __init__(self, payment_class, *args, **kwargs):
self._payment_class = payment_class
self._args = args
self._kwargs = kwargs
self._payment = None
def pay(self, amount):
# 지연 초기화
if not self._payment:
self._payment = self._payment_class(*self._args, **self._kwargs)
# 부가 기능 추가
if self._check_access():
start_time = time.time()
print(f"로깅: {amount}원 결제 시작")
result = self._payment.pay(amount)
end_time = time.time()
print(f"로깅: 결제 완료. 소요 시간: {end_time - start_time:.2f}초")
return result
else:
print("결제 권한이 없습니다.")
def _check_access(self):
# 여기에 실제 접근 제어 로직을 구현합니다.
return True
def process_payment(payment_method: Payment, amount):
payment_method.pay(amount)
naver_pay = NaverPayment()
naver_adpater = NaverAdapter(naver_pay)
naver_pay_proxy = PaymentProxy(NaverAdapter, naver_pay)
toss_pay = TossPayment()
toss_adapter = TossAdapter(toss_pay)
toss_pay_proxy = PaymentProxy(TossAdapter, toss_pay)
process_payment(naver_pay_proxy, 10000)
process_payment(toss_pay_proxy, 5000)
process_payment(naver_pay_proxy, 10000)
process_payment(toss_pay_proxy, 5000)
process_payment
함수는 PaymentProxy
객체를 통해 간접적으로 결제 메서드를 호출할 수 있게 되었고PaymentProxy
의 pay
메서드 내에서 결제 프로세스 전후에 추가적인 로직을 삽입할 수 있게 되었다.이렇게 프록시 패턴을 사용함으로써, 직접적인 메서드 호출을 간접적으로 변경하여 결제 프로세스에 대한 제어가 가능해졌다.
PaymentProxy
클래스 내에서 로깅, 성능 측정 등의 부가 기능을 쉽게 추가할 수 있게 되었다. 이러한 기능들은 원본 결제 클래스를 수정하지 않고도 프록시 내에서 구현될 수 있다
PaymentProxy
클래스는 실제 결제 객체를 즉시 생성하지 않고, 첫 번째 결제 요청(pay
메서드를 호출했을 때)이 있을 때까지 객체 생성을 지연시킨다. 이는 리소스 사용을 최적화하고, 필요한 시점에만 객체를 생성하여 메모리 사용을 효율적으로 관리할 수 있게 한다
PaymentProxy
객체가 생성될 때 실제 결제 객체(_payment
)는 생성되지 않고 None
으로 초기화된다.pay
메서드가 처음 호출될 때, if not self._payment
조건이 True
가 된다.self._payment_class(*self._args, **self._kwargs)
를 통해 실제 결제 객체가 생성되고 self._payment
에 할당된다.pay
메서드 호출에서는 self._payment
가 이미 존재하므로 객체 생성 과정을 건너뛴다.