패턴 태그
[프록시패턴]
패턴 설명
프록시(Proxy)를 번역하면 대리자, 대변인의 의미를 갖고 있다. 대리자, 대변인은 누군가를 대신해서 그 역할을 수행하는 존재이다. 이는 프로그램에도 똑같이 적용된다. 즉, 프록시에게 어떤 일을 대신 시키는 것이다.
어떤 객체를 사용하고자 할때, 객체를 직접적으로 참조하는 것이 아닌 해당 객체를 대항하는 객체를 통해 대상 객체에 접근하는 방식
1. 다른 네트워크에서 실제 리소스 대신 동작하는 클라이언트에 관련된 가상 리소스가 필요(ex. 원격 프록시)
2. 자원에 관한 엑세스를 제어/모니터링하는 경우(ex. 네트워크 프록시, 인스턴스 카운팅 프록시)
3. 직접 엑세스가 보안 문제를 발생시키거나 자원 및 객체를 손상시켜 자원과 객체를 보호해야 할때(ex. 보호프록시, 리버스 프록시)
4. 많은 비용이 드는 계산이나 네티워크 작업 결과의 엑세스를 최적화 해 프록시 캐싱 같이 매번 계산이 수행되지 않게 해야한다.
[프록시 패턴의 장단점]
프록시패턴 장점
- 사이즈가 큰 객체가 로딩되기 전에도 프록시를 통해 참조를 할 수 있다.
- 로컬에 있지 않고 떨어져있는 객체를 사용할 수 있다.
- 원래 객체에 접근에 대해 사전처리를 할 수 있다.
프록시패턴 단점
- 객체를 생성할 때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다.
- 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되어야 하는 경우 성능이 저하될 수 있다.
- 로직이 난해해져 가독성이 떨어질 수 있다.
아래는 프록시 패턴의 종류에 대해서 쭉 정리해보았다.
해당 자료의 좌표는 링크를 걸어 두겠다.(https://johngrib.github.io/wiki/pattern/proxy/#fnref:posa-288)
Remote Proxy
Remote Proxy - 원격 컴포넌트의 클라이언트를 네트워크 주소와 IPC(interprocess communication) 프로토콜로부터 숨겨야 한다. 리모트 프록시(remote proxy)는 서버 측 객체에 대한 클라이언트 측 표현이다.
Protection Proxy
Protection Proxy - 권한을 부여받지 않은 액세스로부터 컴포넌트를 보호해야 한다.
보호 프록시(protection proxy)는 자신과 같은 인터페이스를 구현하는 실객체의 특정 메소드로의 접근을 제어한다. 이 경우 프록시 메소드는 인증 토큰을 전달받고, 이 토큰이 요청한 연산을 인증하지 못하면 예외를 던질 수 있다.
Cache Proxy
Cache Proxy - 다수의 로컬 클라이언트들이 원격 컴포넌트로부터 얻어낸 결과를 공유할 수 있다.
- 캐시 프록시는 캐시를 보관하는 프록시이므로, 캐시 프록시를 구현할 때에는 다음 사항에 대해서도 고민해 보아야 한다.
- 캐시를 어떻게 저장할 것인가?
- 캐시를 어떻게 갱신할 것인가?
- 캐시 유효성을 어떻게 검사할 것인가?
- 구글 크롬이나 파이어폭스 같은 웹 브라우저가 캐시 프록시의 구현을 사용한다고 볼 수 있다.
Synchronization Proxy
Synchronization Proxy - 하나의 컴포넌트에 대해 다수의 유사한 액세스들을 동기화해야 한다.
Counting Proxy
Counting Proxy - 우발적으로 컴포넌트가 삭제되지 않도록 해야하거나 사용량 통계(usage statistics)를 계산해야 한다.
Virtual Proxy
Virtual Proxy - 컴포넌트를 처리하고 로드하는 데 비용이 많이 들 때 컴포넌트에 대한 정보를 분할하는 것이 효과적인 경우가 있다.
가상 프록시 (virtual proxy)는 값비싼 객체를 필요할 때 생성하도록 해준다. 예를 들어 데이터베이스 접근은 데이터가 실제로 사용되기 전까지 프록시가 대신한다. 용량이 큰 이미지를 백그라운드 프로세스에서 네트워크를 통해 가져오는 동안 사용자는 이미지가 이미 그곳에 있다고 생각한다. 이러한 과정을 후기 초기화(lazy instantiation)라 부른다. 가상 프록시는 복사 수정 전략을 구현하는 데에도 유용하다. 객체의 복사 본을 요청받으면 프록시는 단순히 원본 객체에 대한 레퍼런스만을 갖는다그리고 복사본에 대한 수정 요 청이 들어오면 이때 비로소 프록시가 원본 객체를 실 제로 복사하게 된다
위의 예시중에서 나는 카운팅 프록시를 구현해보도록 하겠다.
이 예시는 클래스의 인스턴스를 추적하기 위한 사용법이다. 이 방법을 사용하기 위해 간단한 클래스 몇가지를 만들어서 인스턴스를 만들어보고 해당 인스턴스들을 Proxy클래스에 담아서 인스턴스 카운트를 해보자.
# Accountant.py
from .Employee import Employee
class Accountant(Employee):
def get_role(self):
return 'accountant'
# Admin.py
from .Employee import Employee
class Admin(Employee):
def get_role(self):
return 'administration'
# Engineer.py
from .Employee import Employee
class Engineer(Employee):
def get_role(self):
return 'engineering'
# Employee.py
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
@abstractmethod
def get_role(self):
pass
def __str__(self):
return f'{self.__class__.__name__} - {self.name}, {self.age} years old {self.gender}'
위와 같이 총 4개의 클래스를 만들고 위의 클래스를 Proxy 클래스를 통해서, 각 객체들을 매핑하여, 인스턴스 카운팅을 실시할 것이다.
아래는 마지막 Proxy 클래스이다.
# EmployeeProxy.py
class EmployeeProxy(object):
"""
* Employee 클래스의 인스턴스를 카운팅하기 위해서 만들어진 Proxy 클래스(대리자)
"""
# Count of Employee
count = 0
# __init__과의 차이는 __new__ 메소드는 메모리에 객체를 할당하지만
# __init__ 메소드는 메모리에는 할당하지 않는다.
def __new__(cls, *args):
"""
new 메소드를 활용하여, 메모리에 올려준다.
"""
instance = object.__new__(cls)
cls.incr_count()
return instance
def __init__(self, employee):
self.employee = employee
# 클래스 메소드를 사용하는 이유는 => 클래스가 생성될때 인스턴스의 갯수를 증가를 확인하기 위해서 생성자가 아닌
# 클래스 최상위 속성에 두었기 때문에 클래스의 최상위 속성으로 접근하기 위해서 classmethod를 사용했다.
@classmethod
def incr_count(cls):
"""
* employee 클래스 인스턴스 count UP
"""
cls.count += 1
@classmethod
def decr_count(cls):
"""
* employee 클래스 인스턴스 count down
"""
cls.count -= 1
@classmethod
def get_count(cls):
"""
* employee 클래스 인스턴스 count
"""
return cls.count
def __str__(self):
return str(self.employee)
def __getattr__(self, name):
"""
* 메소드는 인스턴스 속성으로 존재하지 않을때 (즉, 인스턴스 딕셔너리에 없을때)만 호출된다.
다시말해, 존재하는 속성에 접근할 때는 호출되지 않는다.
"""
return getattr(self.employee, name)
def __del__(self):
"""
* 소멸자
"""
# 소멸되면 인스턴스 카운트를 하나 줄여준다.
self.decr_count()
from Architecture.Proxy.EmployeeProxy import EmployeeProxy
from Architecture.Factory.Engineer import Engineer
from Architecture.Factory.Accountant import Accountant
from Architecture.Factory.Admin import Admin
# 팩토리패턴을 사용하여 생성하였으며, 각각의 객체들은 우리가 만든
# Proxy 클래스를 통해서 한번 매핑후에 처리된다.
class EmployeeProxyFactory(object):
"""인스턴스 카운트 프록시를 위한 Employee 팩토리 클래스"""
@classmethod
def create(cls, name, *args):
name = name.lower().strip()
if name == 'engineer':
return EmployeeProxy(Engineer(*args))
elif name == 'accountant':
return EmployeeProxy(Accountant(*args))
elif name == 'admin':
return EmployeeProxy(Admin(*args))
if __name__ == '__main__':
factory = EmployeeProxyFactory()
engineer = factory.create('engineer','sam',25,'M')
print(engineer)
admin = factory.create('admin','sam',25,'M')
print(admin)
print(EmployeeProxy.get_count())
del engineer
print(EmployeeProxy.get_count())
del admin
print(EmployeeProxy.get_count())
위의 프린트 확인해보면 아래와 같은 결과를 얻게된다. engineer, admin을 생성하여, 인스턴스 카운트가 2개가 되는 것을 알 수 있다. 그리고 차근차근 engineer와 admin을 삭제하면 인스턴스의 갯수가 줄어드는 것을 파악할 수 있다.

이렇게 우리는 Proxy패턴을 사용하여 여러가지 클래스들의 인스턴스 카운팅 혹 그 외 다양한 작업들에서 매핑하여 제어할 수 있음을 알 수 있다.
'프로그래밍 > 아키텍처 with python' 카테고리의 다른 글
[Architecture][Python]상속과 서브 클래스 (1) | 2022.05.24 |
---|---|
[Architecture][Python]퍼사드 패턴 (0) | 2022.03.29 |