프록시 패턴

hodu·2024년 8월 26일
0

클린아키텍쳐

목록 보기
5/5
post-thumbnail
post-custom-banner

프록시 패턴(Proxy Pattern)

프록시 패턴은 원본 객체의 인터페이스를 유지하면서 그 객체에 대한 접근을 중개하고 확장하는 구조적 디자인 패턴

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)

어댑터 패턴에서 개선한 코드를 가져와보겠다.

이 코드의 문제점은 다음과 같다.

1. 접근에 대한 제어가 불가능함

  • process_payment함수가 NaverPaymentTossPayment 객체의 메서드를 직접 호출하고 있어 결제 프로세스에 대한 제어가 불가능함.
    process_payment(naver_adpater, 10000)
    process_payment(toss_adapter, 5000)
  • 예를 들어, 결제 전 보안 검사나 권한 확인과 같은 작업을 수행할 수 없음.

    Q. 어댑터 패턴 내의 pay에서 추가해주면 되지 않느냐?

    class TossAdapter(Payment):
        def __init__(self, toss):
            self.toss = toss
    
        def pay(self, amount):
    		    # 여기서 보안 검사, 권한 확인 실시
            return self.toss.payment(amount)
    A. 어댑터 패턴의 역할인터페이스 간 메서드명만 일치시켜주는 것. 여기서 더 추가하면 SRP에 위반됨

2. 부가 기능 추가의 어려움

  • 결제 전후에 로깅, 성능 측정 등의 부가 기능을 추가하기 어려운 구조임.
    • 어떤 클래스에서 해당 기능을 추가해주어야 하는지 명확하지 않음: 코드는 이미 각자의 역할이 잘 분리되어 작성되어 있음
    • 기존 클래스들을 직접 수정해주어야 함

      1. process_payment에서 수정

      def process_payment(payment_method: Payment, amount):
          # 여기서 로깅, 성능 측정
          payment_method.pay(amount)
      결제 방식에 따라 로깅, 성능 측정을 다르게 하고싶지만 모든 결제 방식에 적용되어버리며 SRP가 위반

      2. 어댑터패턴의 pay에서 수정

      class TossAdapter(Payment):
          def __init__(self, toss):
              self.toss = toss
      
          def pay(self, amount):
      		    # 여기서 로깅, 성능 측정
              return self.toss.payment(amount)
      모든 결제 방식에 대해 개별적으로 구현해야 함. 마찬가지로 SRP 위반

3. 지연 초기화 불가능

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)

1. 접근 제어 개선

process_payment(naver_pay_proxy, 10000)
process_payment(toss_pay_proxy, 5000)
  • process_payment 함수는 PaymentProxy 객체를 통해 간접적으로 결제 메서드를 호출할 수 있게 되었고PaymentProxypay 메서드 내에서 결제 프로세스 전후에 추가적인 로직을 삽입할 수 있게 되었다.
  • 이를 통해 결제 전후에 필요한 작업(로깅, 권한 확인 등)을 수행할 수 있는 지점이 생기게 됨.

이렇게 프록시 패턴을 사용함으로써, 직접적인 메서드 호출을 간접적으로 변경하여 결제 프로세스에 대한 제어가 가능해졌다.

2. 부가 기능 추가

PaymentProxy 클래스 내에서 로깅, 성능 측정 등의 부가 기능을 쉽게 추가할 수 있게 되었다. 이러한 기능들은 원본 결제 클래스를 수정하지 않고도 프록시 내에서 구현될 수 있다

3. 지연 초기화

PaymentProxy 클래스는 실제 결제 객체를 즉시 생성하지 않고, 첫 번째 결제 요청(pay 메서드를 호출했을 때)이 있을 때까지 객체 생성을 지연시킨다. 이는 리소스 사용을 최적화하고, 필요한 시점에만 객체를 생성하여 메모리 사용을 효율적으로 관리할 수 있게 한다

  1. PaymentProxy 객체가 생성될 때 실제 결제 객체(_payment)는 생성되지 않고 None으로 초기화된다.
  2. pay 메서드가 처음 호출될 때, if not self._payment 조건이 True가 된다.
  3. 이때 self._payment_class(*self._args, **self._kwargs)를 통해 실제 결제 객체가 생성되고 self._payment에 할당된다.
  4. 이후 pay 메서드 호출에서는 self._payment가 이미 존재하므로 객체 생성 과정을 건너뛴다.
profile
안녕 세계!
post-custom-banner

0개의 댓글