프록시 패턴(Proxy Pattern)

  • 프록시 패턴은 객체 접근을 제어하기 위한 중간 단계에 대리자를 생성하여 처리를 위임하는 패턴입니다.
  • 프록시 패턴의 가장 큰 특징은 하나의 객체를 두 개로 분리하는 것입니다.
    • 그러나 분리된 두 개의 객체는 서로 다른 객체가 아닌 동일한 객체이며 동일한 인터페이스를 갖고 있습니다.
  • 프록시 패턴은 응용이 매우 많습니다. 대표적인 응용 사례는 다음과 같습니다.
    • 원격 프록시
    • 가상 프록시
    • 보호 프록시
    • 스마트 프록시

프록시 패턴 구조

  • 프록시 패턴은 두 객체를 이어준다는 역할에서 **어댑터 패턴(Adapter Pattern)**과 유사합니다.
    • 어댑터 패턴은 서로 다른 인터페이스를 동일하게 맞춰주는 역할입니다.
    • 프록시 패턴은 동일한 인터페이스를 유지하고, 투과적인 성격을 이용하여 객체를 대리합니다.
  • 프록시 패턴은 동일한 인터페이스를 상속받는 또 다른 클래스를 작성합니다.

프록시 패턴: 인터페이스 클래스

  • 동일한 메서드를 사용하기 위해 인터페이스 클래스를 작성합니다.
// 인터페이스
class Subject
{
public:
    virtual void action1() = 0;
    virtual void action2() = 0;
};

프록시 패턴: 객체 구현

  • 인터페이스를 상속받는 객체를 구현합니다.
class RealSubject :
    public Subject
{
public:
    RealSubject()
    {
        std::cout << "RealSubject was concreated" << std::endl;
    }

    void action1() override
    {
        std::cout << "[RealSubject] action1 doing" << std::endl;
    }

    void action2() override
    {
        std::cout << "[RealSubject] action2 doing" << std::endl;
    }
};

프록시 패턴: 프록시 클래스 작성

  • 객체의 역할을 대신할 프록시 클래스를 작성합니다.
  • 복합 객체로 실제 객체를 갖고 있으며, 행위를 대신하는 역할입니다.
class Proxy :
    public Subject
{
public:
    Proxy(std::shared_ptr<RealSubject> realSubject)
    {
        m_realSubject = realSubject;
    }

    void action1() override
    {
        std::cout << "[Proxy] action1 doing" << std::endl;
        m_realSubject->action1();
    }

    void action2() override
    {
        std::cout << "[Proxy] action2 doing" << std::endl;
        m_realSubject->action2();
    }

private:
    std::shared_ptr<RealSubject> m_realSubject;
};

프록시 패턴: 실행 코드

  • 프록시 패턴의 사용 방법을 알아봅니다.
int main(const int argc, const char* argv[])
{
    std::shared_ptr<RealSubject> realSubject = std::make_shared<RealSubject>();
    std::shared_ptr<Proxy> proxy = std::make_shared<Proxy>(realSubject);

    proxy->action1();
    proxy->action2();

    return 0;
}
  • 실행 결과
RealSubject was concreated
[Proxy] action1 doing
[RealSubject] action1 doing
[Proxy] action2 doing
[RealSubject] action2 doing

프록시 패턴 응용

  • 프록시 패턴은 다양하게 활용이 가능합니다.
  • 다른 패턴과의 결합에도 매우 유용한 편입니다.

팩토리 패턴을 적용한 프록시 패턴

  • 프록시 객체의 생성을 팩토리 패턴으로 역할을 위임합니다.
class ProxyFactory
{
public:
    std::shared_ptr<Proxy> getObject()
    {
        std::shared_ptr<RealSubject> real = std::make_shared<RealSubject>();
        return std::make_shared<Proxy>(real);
    }
};

원격 프록시 패턴(Remote-Proxy Pattern)

  • 원격 프록시 패턴은 가장 많이 사용하는 프록시 패턴입니다.
  • 주로 데이터 전달을 목적으로 사용합니다.
  • 원격 프록시 패턴을 위해 사용하던 코드를 수정합니다.
인터페이스 선언
// 인터페이스
class Subject
{
public:
    virtual std::string action1() = 0;
    virtual std::string action2() = 0;
};
인터페이스 객체 구현
class RealSubject :
    public Subject
{
public:
    RealSubject()
    {
        std::cout << "RealSubject was concreated" << std::endl;
    }

    std::string action1() override
    {
        std::cout << "[RealSubject] action1 doing" << std::endl;
        return "RealSubject action1()";
    }

    std::string action2() override
    {
        std::cout << "[RealSubject] action2 doing" << std::endl;
        return "RealSubject action2()";
    }
};
원격 프록시 클래스 작성
  • 동일한 인터페이스를 사용하기 때문에 특정한 기능을 다르게 행동하는 것이 가능합니다.
  • 다른 행동으로 처리가 변경된 기능은 프록시 패턴에서 처리하고, 원래의 행동은 실제 객체로 위임합니다.
class Proxy :
    public Subject
{
public:
    Proxy(std::shared_ptr<RealSubject> realSubject)
    {
        m_realSubject = realSubject;
    }

    std::string action1() override
    {
        std::cout << "[Proxy] action1 doing" << std::endl;
        //m_realSubject->action1();
        return "Replaced proxy action1()";
    }

    std::string action2() override
    {
        std::string msg = m_realSubject->action2();
        if (msg.empty())
        {
            return "Nothing returned by RealSubject action2()...";
        }
        else
        {
            return msg;
        }
    }

private:
    std::shared_ptr<RealSubject> m_realSubject;
};

가상 프록시 패턴(Virtual-Proxy Pattern)

  • 가상 프록시는 프로그램의 실행 속도를 개선하기 위한 패턴입니다.
  • 프로그램이 실행 시 객체들을 생성하고 초기화하는데, 자주 사용하지 않는 객체까지 생성&초기화를 한다면 실행 시간이 길어집니다.
  • 필요할 때 실제 객체를 생성하도록 하는 게으른 초기화(Lazy Initialization)와 유사합니다.
  • 플라이웨이트 패턴(Flyweight Pattern)과 결합하여 많이 사용합니다.
인터페이스 선언
// 인터페이스
class Subject
{
public:
    virtual void action1() = 0;
    virtual void action2() = 0;
};
인터페이스 객체 구현
class RealSubject :
    public Subject
{
public:
    RealSubject()
    {
        std::cout << "RealSubject was concreated" << std::endl;
    }

    void action1() override
    {
        std::cout << "[RealSubject] action1 doing" << std::endl;
    }

    void action2() override
    {
        std::cout << "[RealSubject] action2 doing" << std::endl;
    }
};
가상 프록시 클래스 작성
class Proxy :
    public Subject
{
public:
    void action1() override
    {
        std::cout << "[Proxy] action1 doing" << std::endl;
    }

    void action2() override
    {
        // 게으른 객체 생성
        if (m_realSubject == nullptr)
        {
            real();
        }
        std::cout << "[Proxy] action2 doing" << std::endl;
        m_realSubject->action2();
    }

private:
    void real()
    {
        std::cout << "[Proxy] concreate RealSubject object" << std::endl;
        m_realSubject = std::make_shared<RealSubject>();
    }

    std::shared_ptr<RealSubject> m_realSubject;
};

보호용 프록시 패턴

  • 보호용 프록시는 프록시 객체 생성 시 권한을 입력받습니다.
  • 부여된 권한에 따라 객체 생성 여부 또는 기능 실행을 관리합니다.
  • 인터페이스 선언 및 구현은 이전 코드와 동일합니다.
보호용 프록시 클래스 작성
typedef enum class ePERMIT_T
{
    ALLOW,
    DISALLOW
} ePERMIT;

class Proxy :
    public Subject
{
public:
    Proxy(ePERMIT permit)
    {
        m_permit = permit;
    }

    void action1() override
    {
        std::cout << "[Proxy] action1 doing" << std::endl;
    }

    void action2() override
    {
        if (m_permit == ePERMIT::ALLOW)
        {
            // 게으른 객체 생성
            if (m_realSubject == nullptr)
            {
                real();
            }
            std::cout << "[Proxy] action2 doing" << std::endl;
            m_realSubject->action2();
        }
        else
        {
            std::cout << "[Proxy] disallow action2" << std::endl;
        }
    }

private:
    void real()
    {
        std::cout << "[Proxy] concreate RealSubject object" << std::endl;
        m_realSubject = std::make_shared<RealSubject>();
    }

    std::shared_ptr<RealSubject> m_realSubject;
    ePERMIT m_permit;
};

스마트 프록시 패턴

  • 스마트 프록시 패턴은 실제 객체에 추가 행위를 부여하여 객체를 동적으로 확장하는 패턴입니다.
  • **장식자 패턴(Decorator Pattern)**과 유사한 기법입니다.
  • shared_ptruse_count()와 동일한 원리입니다. (스마트 포인터는 프록시 패턴으로 만들어졌습니다.)
보호용 프록시 클래스 작성
typedef enum class ePERMIT_T
{
    ALLOW,
    DISALLOW
} ePERMIT;

class Proxy :
    public Subject
{
public:
    Proxy(ePERMIT permit)
    {
        m_permit = permit;
        m_count = 0;
    }

    void action1() override
    {
        std::cout << "[Proxy] action1 doing" << std::endl;
        m_count++;
    }

    void action2() override
    {
        if (m_permit == ePERMIT::ALLOW)
        {
            // 게으른 객체 생성
            if (m_realSubject == nullptr)
            {
                real();
            }
            std::cout << "[Proxy] action2 doing" << std::endl;
            m_realSubject->action2();
            m_count++;
        }
        else
        {
            std::cout << "[Proxy] disallow action2" << std::endl;
        }
    }

    // 스마트 프록시
    int getCount()
    {
        return m_count;
    }

private:
    void real()
    {
        std::cout << "[Proxy] concreate RealSubject object" << std::endl;
        m_realSubject = std::make_shared<RealSubject>();
    }

    std::shared_ptr<RealSubject> m_realSubject;
    ePERMIT m_permit;
    int m_count;
};