본문 바로가기

디자인패턴

[디자인패턴] 프록시 패턴

이번 글에서는 프록시 패턴에 대해서 알아보려고 합니다.

  • 프록시와 프록시 패턴
  • 사용예시
  • 프록시 패턴이 적용되지 않은 예제코드
  • 프록시 패턴이 적용된 예제코드
프록시와 프록시 패턴

프록시 패턴을 정리하기에 앞서 프록시와 프록시 패턴은 서로 다르다는 것을 인지하셔야합니다.

프록시 패턴은 프록시를 활용하여 만들어진 패턴이기 때문입니다.

 

먼저, 프록시는 우리말로 대변인이라는 의미를 가지고 있습니다. 대변인은 우리가 해야 할 일을 대신 수행거나 처리해줄 수 있는 역할입니다. 아래 예시를 함께 보도록 하겠습니다.

  1. 아내에게 아기를 씻기기 위해 물좀 받아달라고 하였는데, 아내가 이미 받아 두었다고하여 아기를 바로 씻길수 있음(캐싱, 접근제어)
  2. 음식매장 관리자인 내가 직원에게 설거지만 하고 퇴근하라고 말을하였는데, 직원이 설거지도 하고 주방 청소까지 마무리 하고 퇴근함(부가기능)

이렇게 프록시는 내가 해야 할 행위를 대신 해준다는 의미를 가지고 있습니다.

컴퓨터적인 개념에서 보자면 아래 그림과 같이 클라이언트가 서버에 요청을 직접 호출하는 것이아니라 프록시가 간접적으로 호출해준다고 생각하면 될 것 같습니다.

GOF 디자인패턴에서는 프록시 패턴을 아래와 같이 정의하고 있습니다.

프록시 패턴은 어떤 객체에 대한 접근을 제어하는 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴입니다.

 

사용예시

회사 서비스에서 데이터를 가져오는데 1초이상 걸리는 서비스가 있습니다. 사용자가 증가할수록 이러한 방식은 시스템에 큰 부하를 줄 수 있는 상황이기 때문에 개발팀장님은 이 문제를 해결하기를 요청했고, 개발자들은 프록시 패턴을 사용하여 문제를 해결하려고 하고 있습니다.

 

프록시 패턴이 적용되지 않은 예제코드
@Test
void noProxyTest() {
    RealSubject realSubject = new RealSubject();
    ProxyPatternClient client = new ProxyPatternClient(realSubject);
    client.execute();
    client.execute();
    client.execute();
}

 

public class ProxyPatternClient {

    private Subject subject;

    public ProxyPatternClient(Subject subject) {
        this.subject = subject;
    }

    public void execute() {
        subject.operation();
    }
}

public interface Subject {
    String operation();
}

@Slf4j
public class RealSubject implements Subject{
    @Override
    public String operation() {
        log.info("실제 객체 호출");
        sleep(1000);
        return "data";
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

프록시 패턴 적용 전 - 클래스 의존 관계
프록시 패턴 적용 전 - 객체 의존 관계

테스트 코드를 실행하면 client.execute()를 3번 실행하게 되면서 22초, 23초, 24초와 같이 1초 단위로 객체가 호출 된다는 것을 볼 수있습니다.

만약 이렇게 조회한 데이터가 변하지 않는 데이터라면 데이터를 보관하고 똑같은 호출이 일어날때, 보관되어 있던 데이터를 반환해주는게 성능상 좋은 서비스입니다.

이러한 것을 캐시라고 하는데 프록시 패턴의 주요 기능은 접근 제어입니다.

캐시도 접근제어를 하는 기능 중 하나이기 때문에 프록시 패턴을 활용한다면 이러한 문제를 해결 할 수 있습니다.

 

프록시 패턴이 적용된 예제코드

프록시 패턴이 적용된 코드에서는 CashProxy클래스를 추가하였습니다.

@Test
void cacheProxyTest() {
    RealSubject realSubject = new RealSubject();
    CacheProxy cacheProxy = new CacheProxy(realSubject);
    ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
    client.execute();
    client.execute();
    client.execute();
}
public class ProxyPatternClient {

    private Subject subject;

    public ProxyPatternClient(Subject subject) {
        this.subject = subject;
    }

    public void execute() {
        subject.operation();
    }
}

public interface Subject {
    String operation();
}

@Slf4j
public class CacheProxy implements Subject{

    private Subject target;
    private String cacheValue;

    public CacheProxy(Subject target) {
        this.target = target;
    }

    @Override
    public String operation() {
        log.info("프록시 호출");
        if (cacheValue == null) {
            cacheValue = target.operation();
        }
        return cacheValue;
    }
}

@Slf4j
public class RealSubject implements Subject{
    @Override
    public String operation() {
        log.info("실제 객체 호출");
        sleep(1000);
        return "data";
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
프록시 패턴 적용 후 - 객체 의존 관계
프록시 패턴 적용 후 - 객체 의존 관계

CachProxy클래스를 보면 RealSubject클래스와 2가지 다른점이 있습니다.

 

첫째로는 생성자를 통해 의존성 주입을 받고 있다는 점입니다. 그 이유는 클라이언트가 호출하면 프록시가 최종적으로 실제 객체를 호출해야하기 때문입니다. 따라서 내부에 객체를 참조할 수 있는 target이라는 변수를 선언하였습니다.

 

두번째로는 cacheValue라는 변수입니다. 이 변수는 캐시기능을 위한 변수인데, 만약 클라이언트가 Proxy객체에 operation()메소드를 호출하였을때, cacheValue값이 null이면, 실제 객체에가서 해당 로직을 수행하고 cacheValue값에 가져온 데이터를 저장합니다. 따라서 최초 이후에는 저장된 데이터(cacheValue)를 빠르게 사용할 수 있습니다.

 

테스트 코드를 실행하면 client.execute()를 3번 실행하게 되면서 최초 조회시에는 1초가 걸렸지만, 다음 조회시에는 0.001초 단위로 저장된 데이터를 가져오는 것을 볼 수있습니다.

  1. client의 cacheProxy 호출 cacheProxy에 캐시 값이 없다. realSubject를 호출, 결과를 캐시에 저장 (1초)
  2. client의 cacheProxy 호출 cacheProxy에 캐시 값이 있다. cacheProxy에서 즉시 반환 (0초)
  3. client의 cacheProxy 호출 cacheProxy에 캐시 값이 있다. cacheProxy에서 즉시 반환 (0초)

 

결과적으로 프록시 패턴 도입전에는 3초가 걸렸지만, 프록시 패턴 도입후에는 1초가 걸린 결과를 얻을 수 있었습니다.

 

'디자인패턴' 카테고리의 다른 글

[디자인패턴] 데코레이터 패턴  (1) 2021.11.27
[디자인패턴] 전략 패턴  (0) 2021.11.20
[디자인패턴] 템플릿 메서드 패턴  (0) 2021.11.17