프록시 패턴이란, 특정 객체에 접근하고자 할 때 직접 접근하는 것이 아니라 대리자를 통해 접근하도록 하는 패턴이다.

 

프록시 패턴 (대리자 패턴)

아래의 코드를 보자.

class A
{
public:
    void Death() {}
    void Attack() {}
}

int main()
{
    A* NewA = new A():
    
    if(NewA != nullptr)
    {
        NewA->Attack();
    }
    
    if(NewA != nullptr)
    {
        NewA->Death();
        delete NewA;
        NewA = nullptr;
    }
    
    return 0;
}

 

먼저, A객체를 동적으로 생성해주었다.

 

이후, nullptr체크를 한 뒤 Attack함수를 호출해 주었다.

 

다음으로, nullptr체크를 한 뒤, Death함수를 호출한 뒤, NewA의 메모리를 해제하고 NewA가 가리키는 포인터를 nullptr로 변경해주었다.

 

이 코드에서 문제점을 찾아보자.

 

1. A객체를 알아야 한다.

2. 매번 nullptr 검사를 해주어야 한다.

3. 실제로 객체의 기능을 사용하기 이전부터 객체가 메모리에 존재한다.

 

A객체를 생성하고 그 멤버함수를 호출하기 위해선 당연히 A객체를 알아야 한다.

현재는 main함수에서 A객체의 멤버함수를 호출하고 있지만, 만약 B라는 다른 객체에서 A의 멤버함수를 호출하게 된다면 A객체와 B객체간의 결합도가 높아진다고 할 수 있다.

 

또한, A객체를 값으로 알고 있는 것이 아니라 주소값으로 알고 있기 때문에, 주소값에 접근할 때마다 nullptr검사 등의 유효성 검사를 매번 해주어야 한다. 이는 프로그래머 입장에선 상당히 번거로운 일이라고 할 수 있다.

 

또한, NewA가 실제로 사용되기 이전부터 객체를 생성하여 메모리에 존재하게 되는데, 위의 코드에선 생성한 뒤 바로 Attack함수를 호출하고 있지만, 그 시기가 늦어진다면 Attack 함수가 호출되기 이전까지 의미없는 데이터가 메모리 용량을 차지하고 있게 되는 것이다.

 

이러한 문제점들을 해결하기 위해 프록시 패턴을 사용할 수 있다.

 

아래의 코드를 보자.

class A
{
public:
    void Death() {}
    void Attack() {}
};

class AProxy
{
public:
    void Attack()
    {
        if(NewA == nullptr)
        { 
            NewA = new A();
        }
        
        NewA->Attack();
    }
    
    void Death()
    {
        if(NewA == nullptr)
        {
            std::cout << "A객체가 생성되지도 않았는데, 파괴하려 하였습니다." << std::endl;
            return;
        }
        
        NewA->Death();
        delete NewA;
        NewA = nullptr;
    }
    
private:
	A* NewA = nullptr;    
};

int main()
{
    AProxy* NewProxy = new AProxy();

    NewProxy->Attack();
    NewProxy->Death();

    return 0;
}

A의 기능을 사용하기 위해, A의 멤버함수를 직접 호출하는 것이 아니라 AProxy의 멤버함수를 호출하고 있다.

 

AProxy는 내부에서 A의 기능에 필요한 전처리를 한 뒤 A의 함수를 호출하고 있으며, 함수가 끝난 뒤 후처리가 필요하다면 후처리 또한 실행해주고 있다.

 

이로 인해, A의 기능을 사용할 때 매번 번거롭게 nullptr 검사 등의 전처리 코드를 작성할 필요가 없어지며, A의 객체를 직접 몰라도 AProxy 객체를 통해 A의 기능을 사용할 수 있게 된다. 이로 인해, A객체와 낮은 결합도를 유지한 채로 기능을 사용할 수 있게 된다.

 

또한, 전처리 과정에서 A 객체의 기능을 사용하고자 하는 객체의 권한을 파악하여 접근 허용 여부를 판별하게 되면, 보안 측면의 이점도 노려볼 수 있다. 

 

또한, AProxy의 Attack함수가 호출될 때 A객체를 생성하기 떄문에 A 객체의 생성 시점을 최대한 미룰 수 있으며, 이로 인해 조금 더 효율적인 메모리 사용이 가능해진다.

 

이처럼 A에 직접 접근하여 기능을 사용하지 않고, 다른 객체를 한 단계 거쳐서 A의 기능을 사용하는 디자인 패턴이 프록시 패턴이다. 

 

프록시 패턴에는 위와 같은 장점들이 있지만, 단점도 분명 존재한다.

 

1. 함수 호출 과정에 한 단계가 추가되는 만큼, 성능이 하락할 수 있다.

2. A객체 뿐만 아니라 AProxy 객체도 메모리에 존재해야 하므로, 메모리 사용량 측면에선 비효율적일 수 있다.

3. 함수 호출 과정에 한 단계가 추가되는 만큼, 코드가 복잡해지고 가독성이 떨어질 수 있다.

 

프록시 패턴의 장단점

장점
1. 객체 생성 시점을 뒤로 미룰 수 있다.
2. 전처리, 후처리 등을 기능과 묶어 사용할 수 있다.
3. 객체 간의 결합도를 낮출 수 있다.
4. 보안성을 향상시킬 수 있다.

 

단점
1. 메모리 사용량이 증가한다.
2. 속도 측면의 성능이 하락할 수 있다.
3. 코드가 복잡해지고 가독성이 저하될 수 있다.

 

+ Recent posts