[Design Pattern] 프록시 패턴(Proxy Pattern)

뚜비·2023년 3월 21일
0

Design Pattern

목록 보기
6/9

💻 프록시 패턴 구현 코드


프록시 패턴

"다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리채움자 역할을 하는 객체를 둡니다"
-Gang Of Four-

  • 대상 객체(subject)에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴
  • 프록시(대리, proxy) : 다른 무언가와 이어지는 인터페이스 역할을 하는 클래스
    EX) 네트워크 연결, 메모리 안의 커다란 객체, 파일, 복제할 수 없거나 수요가 많은 리소스의 인터페이스의 역할을 수행
  • 어떤 객체를 사용하고자 할 때, 대상 객체를 직접적으로 참조 하는것이 아닌 대행(대리, proxy)하는 객체를 통해 접근
    → 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있음
    → 실제 객체의 기능이 반드시 필요한 시점까지 객체의 생성을 미룰 수 있음
  • GoF 디자인 패턴 중 구조 패턴에 해당

🤔 프록시 활용 예시

  • 객체의 속성, 변환 등을 보완 → 보안, 데이터 검증, 캐싱, 로깅에 사용
  • 프록시 서버
  • 복합적인 오브젝트들의 다수의 복사본이 존재해야만 하는 상황
    → 프록시 패턴은 애플리케이션의 메모리 사용량을 줄이기 위해플라이웨이트 패턴과 결합된 형태로 나올 수도 있음.
  • EX) 용량이 큰 이미지와 글이 같이 있는 문서를 모니터 화면에 띄울 때
    → 이미지 파일은 용량이 크고 텍스트는 용량이 작다.
    → 텍스트는 빠르게 나타나지만 그림은 조금 느리게 로딩
    → 먼저 로딩이 되는 텍스트라도 먼저 나오는게 좋다. (다 로딩될 때까지 기다리기 힘드니까~)
    → 텍스트 처리용 프로세서, 그림 처리용 프로세스를 별도로 운영해야 함.
    프록시 패턴을 이용~

🤔언제 사용하나요?

  • 객체에 대한 접근을 제어해야 할 때
  • 중요한 개체에 접근할 때 client가 필요한 접근 권한을 가지고 있는지 확인할 수 있어야 할 때
  • 객체에 접근할 때 추가 기능을 제공해야 할 때
  • 무거운 처리(사이즈가 큰 인스턴스 생성 등)를 필요할 때만 처리 하도록 지연시키고 싶을 때


구조



Subject 구현체(Proxy)로 Request() 함수를 호출할 때 RealSubject를 참조하고 있는 Proxy가 RealSubject를 대리하는 구조, 즉 Proxy는 realSubject.operation()으로 호출

  • Subject(주체, interface)
    : Proxy와 RealSubject를 동일시하기 위한 인터페이스(API)를 정의
    : 이 객체를 통해 Client는 Proxy와 RealSubject의 차이를 의식할 필요 없음

  • Proxy(대리인, class)
    : Client의 요구를 할 수 있는 만큼 처리
    : 자신만으로 처리할 수 없으면 RealSubject에게 처리를 맡김 -> 정말 RealSubject가 필요할 때 RealSubject를 생성
    : Subject에서 정해지는 인터페이스(API)를 구현

  • RealSubject(실제 주체, class)
    : Proxy에서 감당할 수 없는 일이 발생했을 때 등장
    : Proxy와 마찬가지로 Subject에서 정해져 있는 인터페이스(API)를 구현

  • Client(의뢰인)
    : Proxy 패턴을 사용하는 역할
    : 보통 Main 클래스를 의미함

❗대리인(Proxy)과 본인(RealSubject)을 분리할 필요가 있는가?

  • 분리 하지 않고 RealSubject에 처음부터 지연평가의 기능(필요하면 그 때 인스턴스를 생성하는 기능)을 추가해도 된다.
  • 그러나 분리해서 프로그램의 부품화를 만들면 개별적으로 수정이 가능 즉, 분할해서 통치!!
  • 즉 Subject 인터페이스에서 선언되어 있는 메소드 중에서 무엇을 Proxy가 처리할 것인지 쉽게 변경 가능할 수 있고 RealSubject에서 수정할 필요가 전혀 없다.

    EX) 지연 평가를 실행시키고 싶지 않다
    Client에서 바로 RealSubject 인스턴스를 생성
    (어차피 Proxy든 RealSubject든 Subject의 인터페이스를 모두 구현하니까)

❗대리와 위임

  • Proxy가 혼자 처리할 수 있는 일은 Proxy가 처리
  • Proxy가 처리할 수 없을 때 RealSubject에 위임한다. (proxy 클래스에서 RealSubject의 메소드를 호출하는 부분에 해당)

투과적이란?

  • Proxy와 RealSubject는 같은 Subject라는 인터페이스로 구현하고 있음
    client는 실제로 호출하는 곳이 Proxy든 RealSubject든 상관하지 않는다.
  • 즉, RealSubject를 직접 이용해도 중간에 Proxy가 들어와도 문제없이 사용 가능
    → 이를 Proxy는 투과적이라고 함!

🤔 Proxy 종류

  • Virtual Proxy(가상 프록시)
    : 정말로 인스턴스가 필요한 시점에서 생성, 초기화를 실행
    : 프록시 클래스에서 자잘한 작업들을 처리하고 리소스가 많이 요구되는 작업들(값비싼 객체)이 필요할 때에만 주체 클래스를 사용하도록 구현
    : 해당 객체가 생성된 것처럼 동작하도록 만들고 싶을때 사용함

    EX) 후기 초기화(lazy instantiation)
    용량이 큰 이미지를 백그라운드 프로세스에서 네트워크를 통해 가져오는 동안(데이터베이스 접근은 데이터가 실제로 사용되기 전까지 프록시가 대신) 사용자는 이미지가 이미 그곳에 있다고 생각한다.

: 가상 프록시는 복사 수정 전략을 구현하는 데에도 유용
→ 객체의 복사 본을 요청받으면 프록시는 단순히 원본 객체에 대한 레퍼런스만을 갖는다그리고 복사본에 대한 수정 요 청이 들어오면 이때 비로소 프록시가 원본 객체를 실 제로 복사하게 된다


  • Remote Proxy(원격 프록시)
    : RealSubject가 네트워크의 상대 쪽에 있음에도 마치 자신의 옆에 있는 것처럼(투과적으로) 메소드를 호출할 수 있음
    원격 객체에 대한 접근을 제어 로컬 환경에 존재하며, 원격객체에 대한 대변자 역할을 하는 객체 서로다른 주소 공간에 있는 객체에 대해 마치 같은 주소 공간에 있는 것처럼 동작하게 만드는 패턴입니다. 예시로 Google Docs를 들 수 있겠습니다. 브라우저는 브라우저대로 필요한 자원을 로컬에 가지고 있고 또다른 자원은 Google 서버에 있는 형태입니다.

  • Protection Proxy(보호 프록시)
    : RealSubject의 기능에 대해서 액세스 제한을 설정! 정해진 사용자이면 메소드 호출을 허가하지만 그 외에는 에러로 처리
    : 주체 클래스에 대한 접근을 제어하기 위한 경우에 객체에 대한 접근 권한을 제어하거나 객체마다 접근 권한을 달리하고 싶을때 사용하는 패턴으로 프록시 클래스에서 클라이언트가 주체 클래스에 대한 접근을 허용할지 말지 결정하도록 할수가 있습니다.


장점

  • 사이즈가 큰 객체(ex : 이미지)가 로딩되기 전에도 프록시를 통해 참조할 수 있다. 따라서 처리 속도가 빠르다.

❗ Example

  • 초기화에 시간이 걸리는 기능이 많이 존재하는 대규모 시스템
    → 기동 시점에서는 이용하지 않는 기능까지 전부 초기화하면 어플리케이션의 기동에 시간이 많이 걸린다는 문제 발생
    → 실제로 해당 기능을 사용할 단계가 되어서 초기화 하도록 하는게 좋다!!
  • 문서 안에 그래픽 오브젝트(화상 등) 를 포함한 문서 에디터
    → 그래픽 오브젝트를 생성하 때 화상 파일을 읽는 등 많은 시간이 걸리기 때문에 모든 그래픽 오브젝트를 생성하는 것은 시간 낭비!
    → 각각의 그래픽 오브젝트를 화면에 표시할 때 생성하는 것이 좋다.
  • 실제 객체의 public, protected 메소드들을 숨기고 인터페이스를 통해 노출시킬 수 있음
  • 로컬에 있지 않고 떨어져 있는 객체를 사용할 수 있음
  • 원래 객체의 접근에 대해서 사전처리할 수 있음

단점

  • 객체를 생성할때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있음
  • 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되야 하는 경우 성능이 저하될 수 있음
  • 로직이 난해해져 가독성이 떨어짐


구현

  • Image (interface)
    ⌞RealImage (Concrete class, Image 구현)
    ⌞ProxyImage (Proxy class, Image 구현)
  • ProxyPatternDemo (Client class, Image object를 load하거나 display할 때 ProxyImage를 호출)

  1. Image 인터페이스를 생성
public interface Image {
   void display();
}

  1. 구체적인 Image 구현
public class RealImage implements Image {

   private String fileName; // Image file 이름 

   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }

   @Override
   public void display() { // 인터페이스 구현
      System.out.println("Displaying " + fileName);
   }

   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}

  1. Proxy 클래스 구현
public class ProxyImage implements Image{

   private RealImage realImage; // 실제 Image class
   private String fileName;

   public ProxyImage(String fileName){
      this.fileName = fileName;
   }

   @Override
   public void display() { // Image 인터페이스 구현 
      if(realImage == null){ // 실제 Image 클래스를 생성
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}

  1. Demo
public class ProxyPatternDemo {
	
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");

      //image will be loaded from disk
      image.display(); 
      System.out.println("");
      
      //image will not be loaded from disk
      image.display(); 	
   }
}
//result
Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg


EXAMPLE

JAVA 어쩌구 저쩌구 - Virtual Proxy

  • 이름있는 프린터 예제임
  • 이름의 설정과 취득에서는 실제 Printer 클래스의 인스턴스 생성 ㄴㄴ / 이름 설정과 취득 부분을 PrinterProxy 클래스가 대리로 실행! 마지막에 print 메소드를 호출해서 실제로 프린터를 실행하는 단계가 되어서야 비로서 PrinterProxy 클래스는 Printer 클래스의 인스턴스를 생성
public interface Printable {
  void setPrinterName(String name);
  String getPrinterName();
  void print(String string);
}
  • Printer 클래스
    : 생성자는 heavyjob을 싱행하고 있음

  • Printable 클래스
    : Printer Proxy와 Printer를 동일시 하기 위한 것

  • PrinterProxy 클래스
    : 대리인
    : setPrinterName과 getPrinterName을 호출해도 Printer 인스턴스는 생성되지 않음
    : Printer 인스턴스가 생성되는 것은 본인(Printer 클래스의 인스턴스)이 정말로 필요할 때
    : Printer 클래스는 PrinterProxy의 존재를 모른다는 점!! 자신이 PrinterProxy를 경유해서 호출되고 있는지 아니면 직접 호출되고 있는지 Printer 클래스는 모른다.



😎 참고자료

면접을 위한 CS 전공지식 노트
Java 언어로 배우는 디자인 패턴 입문
GOF Design Pattern
Proxy pattern
프록시 패턴-위키백과
Design Patterns - Proxy Pattern
프록시 패턴 (Proxy Pattern)-기계인간
[Design Pattern] 프록시 패턴(Proxy Pattern)에 대하여

profile
SW Engineer 꿈나무 / 자의식이 있는 컴퓨터

0개의 댓글