의도
- 다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리채움자 역할을 하는 객체를 둠
다른 이름
- 대리자(Surrogate)
언제 쓰는가 ?
문서 편집기에서 이미지를 불러올 때 이미지에 대한 proxy를 둘 수 있음. 이 proxy의 역할은 문서 편집기는 빠르게 동작하나 이미지 자체가 렌더링 되는 순간에 대해 신경쓰지 않아도 되게 끔 하는 역할을 할 수 있음. proxy 객체는 이미지의 크기가 얼마나 될지도 알고 있어야 하며, 그렇게 함으로서 이미지가 다 띄워지지 않아도 그 공간만큼은 확보할 수 있음.
- 원격지 프록시(remote proxy)는 서로 다른 주소 공간에 존재하는 객체를 가리키는 대표 객체로, 로컬 환경에 위치함.
- 가상 프록시(virtual proxy)는 요청이 있을 때만 필요한 고비용 객체를 생성함. 위 설명의 이미지 proxy가 여기에 해당.
- 보호용 프록시(protection proxy)는 원래 객체에 대한 실제 접근을 제어함. 객체별로 접근 제어 권한이 다를 때 유용할 수 있음.
- 스마트 참조자(smart reference)는 원시 포인터의 대체 객체로 실제 객체에 접근이 일어날 때 추가적인 행동을 함. 1) 참조 횟수 관리 2) 맨 처음 참조되는 시점에 영속적 저장소의 객체를 메모리로 옮김 3) 실제 객체에 접근하기 전에 다른 객체가 그것을 변경하지 못하도록 lock을 검. proxy의 사용예시로서 스마트 포인터가 쓰인다 정도로 이해하면 될 듯. 스마트 포인터 혹은 참조자는 프록시 성격 중 보호용 프록시의 성격을 띄는 것으로 보임.
구조
- Proxy
- 실제로 참조할 대상에 대한 참조자를 관리.
- Subject와 동일한 인터페이스를 제공하여 실제 대상을 대체할 수 있어야 함.
- 실제 대상에 대한 접근을 제어하고 실제 대상의 생성과 삭제를 책임짐.
- Proxy의 종류에 따라 다음을 수행
- 원격지 프록시는 요청 메시지와 인자를 인코딩하여 이를 다른 주소 공간에 있는 실제 대상에게 전달
- 가상의 프록시는 실제 대상에 대한 추가적 정보를 보유하여 실제 접근을 지연할 수 있도록 해야 함
- 보호용 프록시는 요청한 대상이 실제 요청할 수 있는 권한이 있는지 확인함
- Subject : RealSubject와 proxy에 공통적인 인터페이스를 정의
- RealSubject : 프록시가 대표하는 실제 객체
협력 방법
- 프록시 클래스는 자신이 받은 요청을 RealSubject 객체에 전달
결과
- 원격지 프록시는 객체가 다른 주소 공간에 존재한다는 사실을 숨길 수 있음
- 가상 프록시는 요구에 따라 객체를 생성하는 등 처리를 최적화할 수 있음
- 보호용 프록시 및 스마트 참조자는 객체가 접근할 때마다 추가 관리를 책임짐. 객체를 생성할 것인지 삭제할 것인지를 관리함
구현 / 고려사항
- C++에서는 멤버접근 연산자를 오버로드함
- operator-> 연산을 오버로드하면 가독성 좋은 proxy 생성 가능
- proxy가 단순 포인터 역할만 할 때는 이것만으로 충분함
- proxy가 항상 자신이 상대할 실제 대상을 알 필요는 없음
- proxy 클래스는 추상 인터페이스를 통해서만 대상과 일을 하므로, 각 RealSubject 별로 proxy 클래스를 만들 필요가 없음. 그러나 RealSubject의 인스턴스를 생성해야 하는 시점이 되면 어떤 클래스의 인스턴스를 생성해야 하는지 알아야 함.
예제 코드
- proxy의 보호하는 기능에 대해서 예제로 짜봤습니다.
- Computer에 접근하기 위해선 ComputerProxy로만 접근할 수 있게 끔 구성하였고, 컴퓨터는 Mom만 켜고 끌 수 있게 끔 템플릿 메서드 AddAccessible을 구성하였습니다.
#include "Proxy1.h"
int main()
{
ComputerProxy computer_proxy;
Son son;
Mom mom;
computer_proxy.AddAccessible(&mom);
//computer_proxy.AddAccessible(&son); //error!!
computer_proxy.TogglePower(&mom);
computer_proxy.TogglePower(&son);
return 0;
}
- 꺼져있던 컴퓨터가 Mom이 전원을 조작함으로서 켜집니다.
- 접근권한이 없는 son은 컴퓨터의 전원을 만지지 못합니다. 이 때 주석친 코드는 static_assert에 의해 컴파일 타임에 에러가 납니다.
//Proxy1.h
#pragma once
#include <set>
class Person;
class Mom;
class Son;
class Device
{
public:
virtual ~Device() {}
virtual void TurnOn() abstract;
virtual void TurnOff() abstract;
};
class Computer : public Device
{
friend class ComputerProxy;
private:
virtual void TurnOn();
virtual void TurnOff();
private:
Computer() {}
};
class ComputerProxy : public Device
{
public:
~ComputerProxy();
public:
void TogglePower(Person* person);
template<typename T>
void AddAccessible(T* person);
private:
virtual void TurnOn();
virtual void TurnOff();
private:
Device* operator->();
private:
Device* computer = nullptr;
bool bTurnOn = false;
std::set<Person*> AccessiblePeople;
};
class Person
{
};
class Mom : public Person
{
};
class Son : public Person
{
};
template<typename T>
void ComputerProxy::AddAccessible(T* person)
{
static_assert(std::is_base_of_v<Person, T>, "T Must be Derived From Person Class!!");
static_assert(!std::is_same_v<Son, T>, "Son can't access to computer!!");
AccessiblePeople.insert(person);
}
- 일단 컴퓨터에게 공통의 인터페이스를 제공하기 위해 Device 클래스를 만들어 ComputerProxy와 Computer가 각각 상속 받았습니다.
- 이 때 외부에서 전원을 Toggle하는 메서드만 노출하기 위해 Proxy에서 TurnOn, TurnOff에 대해 private으로 세팅해주었습니다.
- Computer는 직접 초기화하지 못하도록 생성자를 private으로 감춰둡니다. 대신 ComputerProxy가 friend 선언을 함으로서 ComputerProxy가 Computer를 생성할 수 있는 유일한 클래스입니다.
- 또한 외부에서 실제 객체(computer)에 직접 접근하지는 못하도록 operator-> 또한 private으로 감추고 operator-> 연산이 호출된 시점에 computer가 nullptr일 경우 생성합니다. 이렇게 구성함으로서 computer에 직접 접근하여 조작하는 것이 아닌 ComputerProxy가 유일한 컴퓨터 생성기이자 컴퓨터 조작 인터페이스가 됩니다.
- 또한 ComputerProxy에 set 컨테이너를 두어 set 컨테이너에 속한 Person 들에 한해서만 ComputerProxy에 접근할 수 있게 구성하였고, 이 때 Person 클래스의 서브클래스 중에 Son에 대해서는 AddAccessible 메서드를 통해서 컨테이너에 포함시키지 못하도록 static_assert를 사용했습니다.
//Proxy1.cpp
#include "Proxy1.h"
#include <iostream>
#include <type_traits>
void Computer::TurnOn()
{
std::cout << "컴퓨터 전원이 켜집니다" << std::endl;
}
void Computer::TurnOff()
{
std::cout << "컴퓨터 전원이 꺼집니다" << std::endl;
}
ComputerProxy::~ComputerProxy()
{
if (computer)
{
delete computer;
}
}
void ComputerProxy::TogglePower(Person* person)
{
if (AccessiblePeople.find(person) == AccessiblePeople.end())
{
std::cout << "접근 권한이 없습니다!!" << std::endl;
return;
}
if (bTurnOn)
{
TurnOff();
}
else
{
TurnOn();
}
}
void ComputerProxy::TurnOn()
{
operator->()->TurnOn();
bTurnOn = true;
}
void ComputerProxy::TurnOff()
{
operator->()->TurnOff();
bTurnOn = false;
}
Device* ComputerProxy::operator->()
{
if (computer == nullptr)
{
computer = new Computer;
}
return computer;
}
- ComputerProxy는 operator->를 통해 computer 객체를 생성하는 책임을 지며, 소멸자를 통해 computer를 만들었다면 지우고 가는 책임까지 집니다.
- ComputerProxy는 단순히 Computer의 메서드를 대리 호출해주는 역할을 하며, 이 때 operator->() 를 통해 호출하게 되면 Device의 인터페이스의 시그니처만 사용하도록 강제하기도 하고, 생성 시점까지 제어할 수 있습니다.
- 또한 실제 외부에 노출되는 유일한 연산인 TogglePower에서는 set 컨테이너에 포함된 person에 대해서만 접근하여 동작할 수 있게끔 구성되어있습니다.
'디자인 패턴 > 구조' 카테고리의 다른 글
플라이급(Flyweight) (0) | 2024.01.02 |
---|---|
퍼사드(Facade) (1) | 2023.12.22 |
장식자(Decorator) (0) | 2023.12.22 |
복합체(Composite) (2) | 2023.12.21 |
가교(Bridge) (2) | 2023.12.21 |