프록시 패턴(Proxy pattern)은 다른 객체를 대신할 객체가 필요한 경우 사용한다. 우리가 잘 알고있는 프록시 서버도 이에 해당이 된다. 프록시 서버는 아래와 같이 실제 서버와 클라이언트 사이의 중계기로서 대리로 통신하는 기능을 수행하는 역할을 맡는다.

 

프록시 패턴의 UML 클래스 다이어그램

  • 서비스 인터페이스(Service Interface): 서비스의 인터페이스를 정의한다. 프록시는 클라이언트가 알아채지 못하도록 서비스 객체로 위장하기 위해 이 인터페이스를 구현해야 한다.

  • 프록시(Proxy): 프록시에는 서비스에 대한 참조가 들어있다. 서비스와 프록시 모두 똑같은 인터페이스를 구현한다. 따라서, 서비스가 들어갈 수 있는 곳이면 프록시로 이를 대체할 수 있다. 여기서 프록시는 흐름 제어만 관여할 뿐 실제 결과값을 조작하거나 변형시켜서는 안된다.

  • 서비스(Service): 실제 작업을 대부분 처리하는 객체.

 

활용 사례

  • 원격 프록시(Remote Proxy): 멀리 떨어진 객체에 대한 로컬 프록시를 제공한다.

  • 가상화 프록시(Virtual Proxy): 실제 사용하려면 비용이 많이 드는 객체를 대신할 경량 객체를 만들어 정말 필요한 경우에만 고비용 객체를 만들 수 있다.

  • 보호 프록시(Protective Proxy): 클라이언트의 접근 권한에 따라 다른 수준의 권한을 부여한다. 프록시는 웹 사이트의 핵심 기능을 허가받지 않은 에이전트로부터 보호한다. 이 프록시 객체는 사용자가 요청에 대한 권한이 있는지 확인한다.

  • 스마트 프록시(Smart Proxy): 클라이언트가 어떤 객체에 접근할 때 추가로 필요한 동작을 수행한다.

 

장점

  • 객체에 대한 보안을 제공한다. 일반적으로 클라이언트는 객체에 직접 접근할 수 없다. 이는 객체가 악의적인 활동에 의해 변형될 수 있기 때문이다. 프록시는 객체를 보호하는 방패 역할을 한다.

  • 다른 서버에 존재하는 외부 객체에 대한 로컬 인터페이스를 제공한다. 분산 시스템 구조에서 클라이언트가 원격으로 특정 커맨드를 권한이 없어 수행하지 못하는 경우가 있다. 이런 경우 로컬 객체(프록시)에 요청을 보내고 프록시는 원격 서버에서 요청을 수행한다.

  • 무거운 객체, 특히 자주 사용되는 객체를 캐싱해 애플리케이션의 성능을 향상시킨다.

 

단점

  • 프록시 패턴을 사용하면 간혹 객체로부터의 응답이 느려지기도 한다. 예를 들어 lazy initialization에 프록시를 사용하고 객체를 처음으로 요청하면, 초기화에 걸리는 시간 때문에 응답 시간이 더 길어질 수 있다.

 

예제 코드

import random
from abc import ABCMeta, abstractmethod


class ServiceInterface:
    """실제 객체와 프록시 객체에 대한 일반적인 인터페이스"""
    __metaclass__ = ABCMeta

    @abstractmethod
    def sort(self, reverse=False):
        raise NotImplementedError


class Service(ServiceInterface):
    """인스턴스화 하는 데 많은 시간과 메모리를 차지하는 커다란 객체에 대한 클래스"""

    def __init__(self):
        self.digits = []

        for i in range(10000000):
            self.digits.append(random.random())

    def sort(self, reverse=False):
        self.digits.sort()
        if reverse:
            self.digits.reverse()


class Proxy(ServiceInterface):
    """Service와 동일한 인터페이스를 갖고 있는 프록시"""
    reference_count = 0

    def __init__(self):
        if not getattr(self.__class__, 'cached_object', None):
            self.__class__.cached_object = Service()
            print('새로운 객체가 생성되었습니다.')
        else:
            print('캐싱된 객체를 사용합니다.')

        self.__class__.reference_count += 1
        print('참조 카운트 = ', self.__class__.reference_count)

    def sort(self, reverse=False):
        """인자는 프록시에 의해 기록됨"""
        self.__class__.cached_object.sort(reverse=reverse)

    def __del__(self):
        """객체에 대한 참조 카운트를 감소시킨다. 만약 참조 카운트가 0이 되면 객체를 삭제한다"""
        self.__class__.reference_count -= 1
        if self.__class__.reference_count == 0:
            print('reference_count가 0입니다. 캐싱된 객체를 삭제하는 중입니다...')
            del self.__class__.cached_object
        print('객체가 삭제되었습니다. 현재 참조 카운트 =', self.__class__.reference_count)


if __name__ == '__main__':
    proxy1 = Proxy()
    proxy2 = Proxy()
    proxy3 = Proxy()

    proxy1.sort(reverse=True)

    print('proxy2를 삭제하는 중입니다..')
    del proxy2

결과:

새로운 객체가 생성되었습니다. 

참조 카운트 =  1 

캐싱된 객체를 사용합니다. 

참조 카운트 =  2 

캐싱된 객체를 사용합니다. 

참조 카운트 =  3 

proxy2를 삭제하는 중입니다.. 

객체가 삭제되었습니다. 현재 참조 카운트 = 2 

객체가 삭제되었습니다. 현재 참조 카운트 = 1 

reference_count가 0입니다. 캐싱된 객체를 삭제하는 중입니다... 

 

객체가 삭제되었습니다. 현재 참조 카운트 = 0

 

'프로그래밍 관련 > 객체 지향 설계' 카테고리의 다른 글

메모. Command pattern  (0) 2019.11.26
메모. Iterator pattern  (0) 2019.11.22
메모. Facade pattern  (0) 2019.11.17
메모. Factory method pattern  (0) 2019.05.19
메모. Builder pattern  (0) 2019.05.18