설계패턴 12. Proxy Pattern

LSDrug·2024년 6월 10일

설계패턴(完)

목록 보기
12/26

1. 정의

"필요해지면 만들기"

  • Proxy는 '대리인'이라는 의미, 일을 대신하는 사람이다.
  • 대리인이 할 수 있는 범위를 넘는 일이 발생하면, 실제 본인에게 와서 업무를 요청한다.
  • 객체지향에서는 바빠서 일을 할 수 없는 real 오브젝트 대신에 프록시 오브젝트가 어느정도 일을 처리한다.
  • real 오브젝트는 프록시 오브젝트의 존재를 모른다. 본인 자신이 프록시를 경유해서 호출되고 있는지 직접 호출되고 있는지를 모른다는 의미이다.
  • 반면에, 프록시 오브젝트는 real 오브젝트를 알고 있다.

2. 등장인물

(1) Subject(주체)의 역할 : 프록시와 RealSubject의 역할을 동일시하기 위한 인터페이스를 결정한다.

(2) Proxy(대리인)의 역할 : 클라이언트의 요구를 할 수 있는 만큼 처리한다. 그러나 처리할 수 없으면 RealSubject에게 처리를 맡긴다.

(3) RealSubject(실제의 주체 - 본인)의 역할 : 프록시가 감당할 수 없는 일이 발생했을 때 처리한다.

(4) Client(의뢰인)의 역할 : 프록시 패턴을 사용하는 역할이다.


3. 사용


주요 사용처는 은행의 DB등이 있다. 이때, 클라이언트가 직접 접근하지 않고 프록시에서 일을 대신 처리한다.


4. 예시

  • 우주를 탐사하는 코드를 프록시 패턴으로 만들어보자.

해당 코드를 만들기 전에 UML을 그려보면 다음과 같다.

그리고 등장인물을 나누면 다음과 같다.

자 이제 코드를 만들어 보자.

from abc import ABCMeta, abstractmethod
import time

class Printable(metaclass=ABCMeta):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def setPrinterName(self, name):
        pass

    @abstractmethod
    def getPrinterName(self):
        pass

    @abstractmethod
    def print(self, msg):
        pass

class PrinterProxy(Printable):
    def __init__(self, name):
        self.name = name
        self.real = None  # 실제 Printer 객체는 초기에 None

    def setPrinterName(self, name):
        if self.real is not None:  
            self.real.setPrinterName(name)
        self.name = name  # 프록시가 이름을 기억

    def getPrinterName(self):
        return self.name

    def realize(self):  # 실제 Printer 객체 생성
        if self.real is None:
            self.real = Printer(self.name)

    def print(self, msg):
        self.realize()  # 필요한 경우 실제 Printer 객체 생성
        self.real.print(msg)  # 실제 Printer 객체에 작업 위임

class Printer(Printable):
    def __init__(self, name):
        self.heavyJob("Printer 인스턴스(" + name + ") 생성 중")  # 무거운 작업 시뮬레이션
        self.name = name

    def setPrinterName(self, name):
        self.name = name

    def getPrinterName(self):
        return self.name

    def print(self, msg):
        print(self.name + ":", msg)

    def heavyJob(self, msg):  # 무거운 작업 시뮬레이션
        print(msg)
        for i in range(5):
            time.sleep(1)
        print("완료")

# 클라이언트 코드 (이미지에 있는 코드 포함)
p = PrinterProxy("보스토크")
print("현재 이름은 " + p.getPrinterName() + "입니다.")

p.setPrinterName("머큐리")
print("현재 이름은 " + p.getPrinterName() + "입니다.")
p.print("hello, How are you?")
p.print("Do you hear me?")

p.setPrinterName("아폴로")
print("현재 이름은 " + p.getPrinterName() + "입니다.")
p.print("We successfully arrived the moon.")

5. 다양한 Proxy

  • 프록시는 가능한 일만 처리하지만, 실제로 print할 때까지 무거운 처리를 지연시킬 수 있다.
  • 가령 대규모 시스템을 생각해보면, 기동시점에서 이용하지 않는 기능까지 전부 초기화하면, 어플리케이션의 기동에 시간이 많이 걸리는 문제가 있다. 이는 사용자의 불만으로 이어진다.
  • 따라서, 실제로 사용하는 시점, 그 단계가 되었을 때 처음으로 초기화 하는 편이 스트레스를 줄여준다.

Proxy와 Real을 분리할 필요가 있을까?

  • 가령 printer 클래스 안에 처음부터 지연평가 기능을 넣을 수 있다.
  • 그러나, 둘은 분리해서 프로그램을 부분화하면, 개별적으로 수정이 가능하다는 장점이 있다.

6. 투과적 API

  • PrinterProxy 클래스와 Printer 클래스는 같은 Printable이란 인터페이스를 구현하고 있다.
  • Main 클래스는 실제로 호출하는 곳이 PrinterProxy 클래스이든, Printer 클래스이든 상관하지 않는다. Printer를 직접 이용해도 중간에 PrinteProxy가 들어와도 문제없이 사용이 가능하다는 의미이다.
  • 이러한 경우 PrinterProxy 클래스는 투과적이라고 말할 수 있다.

profile
마약같은 코딩, 마약같은 코딩러

0개의 댓글