FireDrago

프록시 패턴과 데코레이터 패턴 본문

프로그래밍/디자인패턴

프록시 패턴과 데코레이터 패턴

화이용 2024. 5. 13. 12:33

프록시

프록시란 '대리자'란 의미를 가지고 있다. 클라이언트가 서버에게 요청할때, 프록시를 통해서 간접적으로 요청할 수 있다.

프록시와 서버는 같은 인터페이스를 구현하거나, 프록시가 서버를 상속하여, DI (의존성 주입)을 통해 호출한다.

DI를 사용하면, 클라이언트의 코드를 전혀 변경하지 않고 프록시를 사용할 수 있고, 프록시 사용 여부조차 클라이언트는 알 수 없다. 프록시는 단순 대리자의 역할 뿐만 아니라 두가지 주요 기능을 제공한다. 

<프록시가 제공하는 기능>

1. 접근제어

  - 권한에 따른 접근 차단

  - 캐싱

  - 지연로딩

2. 부가 기능 추가

  - 예) 실행 시간 측정

  - 예) 요청값이나 응답 값을 중간에 변형

 

프록시를 사용한 두가지 디자인 패턴

1. 프록시 패턴 : 프록시를 사용하고, 접근제어가 목적

2. 데코레이터 패턴 : 프록시를 사용하고, 부가기능 추가가 목적

 

프록시 패턴

프록시 객체가 RealSubject와 같은 인터페이스 Subject를 구현하여 클라이언트가 Proxy객체를 호출하는 코드이다.

프록시 패턴은 접근제어의 목적이 강하다.

 

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();
        }
    }
}

@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;
    }
}

 

private Subject target : 클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야한다. 

따라서 내부에 실제 객체의 참조를 가지고 있어야 한다. 프록시가 호출하는 대상을 target 이라 한다

 

코드를 보면 cacheValue 에 값이 없으면 실제 객체( target )를 호출해서 값을 구한다. 

그리고 구한 값을 cacheValue 에 저장하고 반환한다. 만약 cacheValue 에 값이 있으면 실제 객체를 전혀
호출하지 않고, 캐시 값을 그대로 반환한다. 따라서 처음 조회 이후에는 캐시( cacheValue )에서 매우 빠르게 데
이터를 조회할 수 있다. 접근제어의 캐시기능을 활용한 프록시 패턴을 사용했다.

 

@Test
void cacheProxyTest() {
    Subject realSubject = new RealSubject();
    // 프록시생성시, 실제객체 넘겨주기
    Subject cacheProxy = new CacheProxy(realSubject);
    // 클라이언트는 프록시인지 실제객체인지 모른채 호출
    ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
    client.execute();
    client.execute();
    client.execute();
}

 

 

데코레이터 패턴

데코레이터 패턴도 프록시 패턴과 비슷하다. 내부에서 프록시를 사용한다. 다만 부가기능에 더 초점을 맞출 뿐이다.

 

public interface Component {
    String operation();
}

// 실제 객체
@Slf4j
public class RealComponent implements Component{
    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}

@Slf4j
public class MessageDecorator implements Component{

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("MessageDecorator 실행");
		// 실제객체 operation() 실행 결과값에 문자를 추가하는 로직 (부가기능) 
        String result = component.operation();
        String decoResult = "*****" + result + "*****";
        log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result, decoResult);

        return decoResult;
    }
}

 

MessageDecoratorComponent 인터페이스를 구현한다. 프록시가 호출해야 하는 대상을 component 에 저장한다.
operation() 을 호출하면 프록시와 연결된 대상을 호출( component.operation()) 하고, 그 응답 값에

***** 을 더해서 꾸며준 다음 반환한다.

 

@Test
void decorator1() {
    Component component = new RealComponent();
    Component messageDecorator = new MessageDecorator(component);
    DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
    client.execute();
}

 

 

프록시 패턴 vs 데코레이터 패턴

 

프록시를 사용하고 해당 프록시가 접근 제어가 목적이라면 프록시 패턴이고, 

새로운 기능을 추가하는 것이 목적이라면 데코레이터 패턴이 된다.