[Design pattern] 2-6. 프록시 패턴 (Proxy pattern)


Study/Design Pattern  2021. 11. 1. 19:41

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 프록시 패턴(Proxy pattern)에 대한 글입니다.


프록시 패턴은 어떻게 보면 데코레이터 패턴과 유사한 구조를 구성하고 있습니다만, 데코레이터 패턴은 상속받은 인터페이스에 생성자에서 같은 인터페이스를 상속 받은 인스턴스를 받아 내용을 추가하는 내용이라면, 프록시 패턴은 상속받은 인터페이스에 클래스 내부에서 같은 인터페이스를 상속받은 인스턴스를 생성하는 패턴입니다.

출처 - https://en.wikipedia.org/wiki/Proxy_pattern

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  virtual ~INode() {};
};
// INode를 상속 받은 Node 클래스
class Node : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout << "Node Class" << endl;
  }
};
// Proxy 클래스
class NodeProxy : public INode {
private:
  // 맴버 변수
  INode* node;
public:
  // 생성자
  NodeProxy() {
    node = new Node();
  }
  // 소멸자
  ~NodeProxy() {
    delete node;
  }
  // 함수 재정의
  virtual void print() {
    // Node 인스턴스의 print 함수 호출
    node->print();
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  INode* node = new NodeProxy();
  // 함수 호출
  node->print();
  // 메모리 삭제
  delete node;
  return 0;
}

위 구조가 프록시 패턴의 기본 구조입니다. NodeProxy 클래스의 생성자에서 INode 인터페이스를 상속 받은 Node 클래스의 인스턴스를 생성합니다. 그리고 print 함수에서는 Node 인스턴스의 print 함수를 호출합니다.

보통은 Node 클래스를 NodeProxy 클래스의 인라인 클래스로도 만듭니다.

위처럼만 보면 굳이 왜 이렇게까지 만들까 하는 생각이 들겠네요. 그냥 Proxy클래스가 아닌 Node 클래스를 그냥 사용해도 상관없을 것 같은데...

// 인터페이스
interface INode {
  // 추상 메소드
  void print();
}

// INode를 상속받은 Node1클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node1 class");
  }
}

// INode를 상속받은 Node2클래스
class Node2 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node2 class");
  }
}

// Proxy 패턴 클래스
class NodeProxy implements INode {
  // 맴버 변수
  private INode node;

  // 생성자 - 파라미터가 없으면 true
  public NodeProxy() {
    this(true);
  }

  // 생성자
  public NodeProxy(boolean check) {
    // 파라미터 check에 의해 생성되는 인스턴스가 다르다.
    if (check) {
      // Node1 인스턴스 생성
      node = new Node1();
    } else {
      // Node2 인스턴스 생성
      node = new Node2();
    }
  }
  // 함수 재정의
  public void print() {
    // node 인스턴스의 print 함수 실행
    node.print();
  }
}

// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // 파라미터가 없는 NodeProxy 인스턴스 생성
    INode node = new NodeProxy();
    // print 함수 호출
    node.print();
    // 파라미터가 false인 NodeProxy 인스턴스 생성
    node = new NodeProxy(false);
    // print 함수 호출
    node.print();
  }
}

Proxy 클래스의 생성자 파라미터에 의해 내부 맴버 변수의 INode node에 생성된 인스턴스가 다릅니다.

즉, 파라미터나 데이터에 의해 인스턴스를 구분을 할때 사용되는 패턴이라고 할 수 있습니다.

using System;
// 인터페이스
interface INode
{
  // 추상 메소드
  void Print();
}
// Proxy 패턴 클래스
class NodeProxy : INode
{
  // INode를 상속받은 Node1클래스
  private class Node1 : INode
  {
    // 함수 재정의
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node1 class");
    }
  }

  // INode를 상속받은 Node2클래스
  private class Node2 : INode
  {
    // 함수 재정의
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node2 class");
    }
  }

  // 맴버 변수
  private INode node;
  private bool check;
  // 생성자
  public NodeProxy(bool check = true)
  {
    this.check = check;
  }

  // 함수 재정의
  public void Print()
  {
    if(node == null)
    {
      // 파라미터 check에 의해 생성되는 인스턴스가 다르다.
      if (check)
      {
        // Node1 인스턴스 생성
        node = new Node1();
      }
      else
      {
        // Node2 인스턴스 생성
        node = new Node2();
      }
    }
    // node 인스턴스의 print 함수 실행
    node.Print();
  }
}

// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(String[] args)
  {
    // 파라미터가 없는 NodeProxy 인스턴스 생성
    INode node = new NodeProxy();
    // print 함수 호출
    node.Print();
    // 파라미터가 false인 NodeProxy 인스턴스 생성
    node = new NodeProxy(false);
    // print 함수 호출
    node.Print();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

이번에는 Node1 클래스와 Node2 클래스를 NodeProxy클래스의 인라인 클래스로 작성했습니다.

그리고 인스턴스를 생성자에서 생성하는 것이 아닌 Print 함수에서 생성합니다. 즉, 만약 Node 클래스가 많은 데이터를 가지고 있거나 리소스를 사용하는 클래스라고 한다면, 위처럼 생성자가 아닌 함수 호출할 때 인스턴스를 생성함으로 성능에 고려한 설계를 할 수 있는 장점도 있습니다.


제 생각으로 패턴 구조로는 생성 패턴에서는 Factory 메서드 패턴과 Flyweight 패턴, 데코레이터 패턴을 자주 사용하는 상황에서 굳이 프록시 패턴이 필요할까 생각이 많이 드는 패턴입니다. 그래서 그런지 실제적으로 많이 사용되는 패턴은 아닙니다.

또, 그냥 비슷하게 인터페이스를 상속 안받고 맴버 변수에 여러 클래스의 인스턴스를 넣어서 경우로 많이 사용합니다. 그러나 사양에 따라 프록시 패턴이 더 최적인 경우도 많이 있으니 사양에 맞게 패턴을 적용하면 좋을 것 같습니다.


여기까지 디자인 패턴의 프록시 패턴(Proxy pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.