Computer Science/디자인 패턴

프록시(Proxy) 패턴에 대해 알아보자.

TwinParadox 2023. 2. 19. 23:39
728x90

프록시 패턴(Proxy Pattern)?

특정 객체로부터의 "접근을 제어"하는 대리인(객체)을 제공한다.

어떤 객체를 사용하려고 할 때, 객체를 직접 참조하는 것이 아니라 해당 객체에 대응하는 일종의 대리인 역할을 하는 객체를 통해서 대상에 접근하는 방식으로, 직접 참조하려던 객체가 메모리에 존재하지 않아도 기본적인 정보 참조나, 실제 사용 시점까지 생성 시점을 미루는 등의 행위가 가능해진다.

 

대표적인 세 가지 종류

원격 프록시

다른 JVM 상에서 존재하는 객체를 대신하는 로컬 객체를 만들어 사용한다. 로컬 객체에 있는 프록시 메서드를 호출하게 되면 원격 객체의 메서드를 호출하여 관련 작업을 처리하고 이 처리 결과를 넘겨받아서, 실제 로컬 환경에 원격 객체가 존재하는 것처럼 동작하게 하는 방법이다.

 

 

가상 프록시

주로 생성하는 데 많은 비용이 드는 객체를 대신하여 동작하게 하려고 할 때 사용한다. 복잡하고 무거운 객체가 진짜로 필요한 시점까지 생성을 미루는(lazy initailization)을 제공하며, 이 객체가 생성되기 전이나 생성되는 중에 객체를 대신하다가, 실제 생성이 완료되면 생성된 객체가 처리하도록 한다.

 

 

보호 프록시

접근 권한을 바탕으로 객체로의 접근을 제어하고 싶을 때 사용한다. 클라이언트의 접근 권한에 따라서 주체 클래스에 대해서 호출할 수 있는 메서드 등을 제어하도록 프록시 클래스를 설계하여 사용한다.

 

 

흉내 내보기

Proxy UML - Wikipedia

프록시 패턴 UML은 단순하고 이해하기도 쉽다. 구조를 살펴보자.

  • Proxy와, RealSubject는 같은 인터페이스를 구현한다.
  • 우리는 Proxy 객체의 doAction 메서드를 호출한다.
  • Proxy 객체는 RealSubject doAction 메서드를 호출을 대신한다. 이때, RealSubject가 생성되지 않은 상태라면, 생성까지 하고 호출한다.

 

ExpensiveObject 인터페이스

public interface ExpensiveObject {
    void doAction();
}

 

ExpensiveObject 구현체

public class ExpensiveObjectImpl implements ExpensiveObject {

    public ExpensiveObjectImpl() {
        heavyInitialConfiguration();
    }
    
    @Override
    public void doAction() {
        LOG.info("processing complete.");
    }
    
    // 생성하는 데 비용이 크다고 하자.
    private void heavyInitialConfiguration() {
        LOG.info("Loading initial configuration...");
    }
    
}

 

ExpensiveObject 프록시

public class ExpensiveObjectProxy implements ExpensiveObject {
    private static ExpensiveObject object;

    @Override
    public void doAction() {
        if (object == null) {
            object = new ExpensiveObjectImpl();
        }
        object.doAction();
    }
}

 

프록시 객체를 통한 doAction 호출과 결과

public static void main(String[] args) {
    ExpensiveObject object = new ExpensiveObjectProxy(); // 프록시로 생성
    object.doAction(); // 생성 & doAction
    object.doAction(); // doAction
}

/*
Loading initial configuration...
processing complete.
processing complete.
*/

프록시 객체를 생성해서 doAction 메서드를 호출했는데, 실제 객체까지 생성하고 그 객체에 있는 doAction까지 호출한 결과를 확인해 볼 수 있다. 우린 프록시에게 시켰지만, 프록시가 ExpensiveObject 객체까지 생성해서 doAction을 대신시킨다는 것에 초점을 맞춰보자.

 

 

데코레이터 패턴(Decorator Pattern)이랑 다른 게 무엇인가...?

접근 제어는 프록시 패턴, 기능 추가는 데코레이터 패턴

프록시는 프록시 패턴에 한정적인 내용은 아니다. 데코레이터 패턴에서도 프록시를 사용한다. 둘 다 똑같이 프록시를 사용하는데, 이 프록시를 통해서 앞서 계속 이야기해왔던 접근 제어 말고도, 기능을 추가할 수 있다. 바로 이 기능을 추가하는 것을 목표로 하는 것이 데코레이터 패턴이다. 접근 제어와 기능 추가, 둘 중 어느 것을 목적으로 하느냐에 따라 다르다.

 

 

마무리하며...

이 프록시 패턴을 언제 사용하는 게 좋을지 생각해 보자.

  • 객체 접근에 대한 제어, 사전 처리가 가능하게끔 하는 목표를 달성할 수 있다.
  • 디테일하게 다루면, 앞서 말했던 세 가지 대표적인 프록시 패턴 종류의 장점들을 취할 수 있다.
    • 생성 전에 프록시를 통한 참조가 가능해진다. - 가상 프록시
    • 실제 객체의 메서드를 숨기고 인터페이스로만 노출 가능하다. - 보호 프록시
    • 원격 객체를 사용할 수 있다. - 원격 프록시

역시 잘못 사용하면 문제가 될 수 있으니, 다음과 같은 케이스에선 조심해야 된다.

  • 객체 생성 자체가 빈번하면, 결국 프록시 객체의 생성도 일종의 오버헤드로 성능 문제를 유발한다.
  • 프록시 내부에서 객체 생성을 위해 스레드가 생성되거나, 동기화가 필요하면 이것 역시 성능 문제를 유발한다.
  • 무언가를 대신 처리한다는 그것 과도해지면 로직이 난해해진다. 프록시가 과하게 중첩된다면 설계에 대해 고민해야 한다.

 

 

Reference

헤드 퍼스트 디자인 패턴(개정판)

https://en.wikipedia.org/wiki/Proxy_pattern

https://www.baeldung.com/java-proxy-pattern

728x90
728x90