개발놀이터

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

Spring/Spring

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

마늘냄새폴폴 2023. 6. 27. 23:09

이번 포스팅에는 프록시 패턴과 데코레이터 패턴에 대해서 알아보겠습니다. 뜬금없이 왜 프록시 패턴과 데코레이터 패턴을 공부했냐면 제가 AOP를 공부했기 때문입니다. 

 

스프링 AOP에서 사용하는 GOF 디자인 패턴 중 데코레이터 패턴이 사용되었기 때문에 공부차 한번 구현해봤습니다. 

 

한번 시작해보죠!

 

프록시

이 두 패턴을 알아보기 전에 프록시에 대해서 알아봐야 합니다. 

 

보통 프록시는 '대리자' 라고 번역이 되고 내가 수행해야 할 일을 대신 수행해주는 존재 라고 생각하시면 됩니다. 

 

이렇게 대신 내 일을 대신 수행해주면 무슨 이점이 있을까요? 

 

프록시를 이용하면 얻을 수 있는 것은 두 가지 입니다. 

 

  • 접근 제어 (캐싱)
  • 부가 기능 (로깅, 트랜잭션)

 

접근 제어

예를 들어 클라이언트가 서버에 A라는 데이터를 조회했다고 가정해봅시다. 그럼 서버는 클라이언트에게 A를 전달하겠죠. 다시 조회하면? 다시 전달합니다. 

 

이 때 프록시가 있으면 어떻게 될까요? 

 

이렇게 프록시가 접근을 제어할 수 있게 됩니다. 이렇게 되면 클라이언트는 두 번째엔 처음보단 빠르게 데이터를 받을 수 있겠죠? 

 

이제 코드로 구현해보겠습니다. 그 전에 의존관계를 확인해볼 필요가 있습니다. 

 

 

프록시와 서버는 같은 인터페이스를 구현한 객체입니다. 이렇게 되면 클라이언트는 인터페이스에만 의존하기 때문에 내가 프록시를 호출했는지 서버를 호출했는지 알 수 없습니다. 

 

이것이 바로 느슨한 결합도를 유지한다는 것이죠. 

 

이제 프록시와 서버를 보면 프록시는 서버를 호출해야 합니다. 그래야 접근을 제어할 수 있기 때문이죠. 

 

위에 있는 이 그림을 다시 보시면 클라이언트는 프록시를 프록시는 서버를 호출하면서 클라인어트와 서버 사이에 대리자로서 접근을 제어하는 것입니다. 

 

이 그림에서 보면 클라이언트는 프록시를 (정확히는 인터페이스를) 프록시는 서버를 호출하는 것입니다. 

 

이제 본격적으로 코드로 확인해보시죠. 

 

@Slf4j
public class Service {

	private final Proxy proxy;
    
    public Service(Proxy proxy) {
    	this.proxy = proxy;
    }
	
    public void method() {
    	log.info("Service 호출");
        proxy.call();
    }
}

public interface Proxy {
	
    String call();
}

@Slf4j
public class ProxyObject implements Proxy {
	
    private final RealObject target;
    private String cacheValue;
    
    public ProxyObject(RealObject target) {
    	this.target = target;
    }
    
    @Override
    public String call() {
    	log.info("프록시 객체 호출");
        
        if (cacheValue == null) {
        	cacheValue = target.call();
        }
        
        return cacheValue;
    }
}

@Slf4j
public class RealObject implement Proxy {
	
    @Override
    public String call() {
    	log.info("실제 객체 호출");
        sleep(1000);
        return "data";
    }
}

실제 객체인 RealObject의 call()은 실행시간이 1초정도 걸리는 로직이라고 가정합니다. 

 

위의 코드는 서버와 프록시를 구현한 코드입니다. 이제 클라이언트를 구현해보죠. 

 

@Test
void test() {
    RealObject realObject = new RealObject();
    ProxyObject proxyObject = new ProxyObject(realObject);
    Service service = new Service(proxyObject);
    
    service.method();
    service.method();
    service.method();
}

자 이제 실행해보면?

 

원래였으면 3초가 걸렸어야할 로직이 1초만에 끝나는 것을 알 수 있습니다. 

 

맨 처음에만 캐싱된 데이터가 없으니 실제 객체를 호출하지만 그 이후부턴 프록시가 호출되어 곧바로 리턴되는 것을 볼 수 있습니다. 

 

 

부가 기능

부가 기능은 앞서 봤던 접근 제어와 거의 흡사합니다. 개념은 접근 제어와 일치하기 때문에 생략하고 바로 코드로 넘어가겠습니다. 

 

@Slf4j
public class Service {

	private final Proxy proxy;
    
    public Service(Proxy proxy) {
    	this.proxy = proxy;
    }
	
    public void method() {
    	log.info("Service 호출");
        proxy.call();
    }
}

public interface Proxy {
	
    String call();
}

@Slf4j
public class ProxyObject implements Proxy {
	
    private final RealObject target;
    
    public ProxyObject(RealObject target) {
    	this.target = target;
    }
    
    @Override
    public String call() {
    	log.info("프록시 객체 호출");
        
        String data = target.call();
        String decorator = "*****" + data + "*****"
        return decorator;
    }
}

@Slf4j
public class RealObject implement Proxy {
	
    @Override
    public String call() {
    	log.info("실제 객체 호출");
        return "data";
    }
}

달라진건 캐시 데이터가 없다는 것과 실제 객체에 1초 소요가 없다는 것 그리고 그 외에는 전부 똑같습니다. 

 

이제 클라이언트 코드를 작성해보겠습니다. 

 

@Test
void test() {
	RealObject realObject = new RealObject();
    ProxyObject proxyObject = new ProxyObject(realObject);
    Service service = new Service(proxyObject);
    
    service.method();
}

결괏값이 이쁘게 꾸며져 나오는 것을 확인할 수 있습니다. 

 

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

앞서 우리는 접근제어, 부가기능 추가라는 관점에서 프록시를 이용해봤습니다. 접근 제어는 프록시 패턴, 부가 기능 추가는 데코레이터 패턴이라고 불립니다. 

 

그런데 조금 이상하죠? 둘이 구현하는 방법이나 사용 방법이 너무 똑같은데 이 둘을 구분해야 할까요?

 

GOF패턴에선 패턴들을 의도에 맞게 구별합니다. 접근 제어의 의도를 가지고 있다면 프록시 패턴, 부가 기능을 추가하는 의도를 가지고 있다면 데코레이터 패턴이라고 부르는 것이죠. 

 

※주의

프록시 패턴이라는 이름 때문에 헷갈릴 수도 있지만 프록시 패턴만 프록시를 사용하는 것이 아닙니다! 프록시 패턴, 데코레이터 패턴 모두 프록시를 이용하는 패턴이고 이름만 프록시 패턴, 데코레이터 패턴이라고 명명한 것 뿐이죠. 

 

 

마치며

이번 포스팅에선 AOP를 포스팅하기 전 내용으로 프록시 패턴과 데코레이터 패턴에 대해서 알아봤습니다. AOP에선 프록시 패턴보단 데코레이터 패턴에 더 가깝긴 하지만 둘 다 프록시를 이용한 패턴이니만큼 실제로 구현해봤습니다. 

 

GOF 디자인 패턴을 시간이 되면 본격적으로 공부해야겠다고 생각이 드는 시간이었습니다. 나중에라도 한번 제대로 배워봐야겠네요.