개발놀이터

프록시 패턴 본문

리팩토링/GOF 디자인패턴

프록시 패턴

마늘냄새폴폴 2022. 7. 8. 06:00

프록시 패턴은 무엇인가?

프록시 패턴은 인터페이스를 사용하고 실행시킬 클래스에 대해 객체가 들어갈 자링네 대리자 객체를 대신 투입하여, 클라이언트는 실제 실행시킬 클래스에 대한 메소드를 반환하는지 대리 객체의 메소드를 반환하는지를 모르게 하는 것을 말한다. 

 

프록시 패턴은 왜 사용할까? 프록시 패턴은 전처리 및 후처리 사용에 용이하고, 특정 메서드에 대한 보안이 좋다.

 

하지만 프록시 패턴을 사용시 가독성이 떨어질 수 있다는 단점도 존재한다. (런타임 의존도가 복잡하기 때문)

 

프록시 패턴은 지연 로딩을 사용하거나, 필요할 때 객체를 추가시키고 싶을 때 사용하면 좋다. 또한 보안 문제로 인하여 특정 메서드의 접근을 제어하고 싶지 않을 때도 사용할 수 있다. 

 

프록시 패턴의 구조

 

 

프록시 패턴 예제

1. Subject 인터페이스를 만든다. 

public interface Subject {
	String operation();
}

 

2. RealSubject 클래스를 만든다 (Subject 인터페이스의 구현체)

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

 

3. ProxyPatternClient 클래스를 만든다. 

public class ProxyPatternClient {
	
    private Subject subject;
    
    public ProxyPatternClient(Subject subject) {
    	this.subject = subject;
    }
    
    public void execute() {
    	subject.operation();
    }
}

 

4. 테스트 클래스를 만든다. 

public class ProxyPatternTest {
	
    @Test
    void noProxyTest() {
    	RealSubject realSubject = new RealSubject();
        ProxyPatternClient client = new ProxyPatternClient(realSubject);
        client.execute();
        client.execute();
        client.execute();
    }
}

 

5. 실행 결과

 

client가 realSubject를 세번 호출해서 값을 조회하는데 걸린 시간은 3초이다. 

 

그런데 이 데이터가 한번 조회하면 변하지 않는 데이터라면 어딘가에 보관해두고 이미 조회한 데이터를 사용하는 것이 성능상 좋다. 이런 것을 캐시라고 한다. 

 

프록시 패턴의 주요 기능은 접근 제어이다. 캐시도 접근 자체를 제어하는 기능 중 하나이다. 

 

이미 개발된 로직을 전혀 수정하지 않고, 프록시 객체를 통해서 캐시를 적용해보자

 

6. CacheProxy 클래스 만들기

@Slf4j
public class CacheProxy implements Subject {
	
    private Subject target;
    private String cacheValue;
    
    public CacheProxy(Subject target) {
    	this.target = target;
    }
    
    @Override
    ipublic String operation() {
    	log.info("프록시 호출");
        if (cacheValue == null) {
        	cacheValue = target.operation();
        }
        return cacheValue;
	}
}

-private Subject target : 클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야 한다. 따라서 내부에 실제 객체의 참조를 가지고 있어야 한다. 이렇게 프록시가호출하는 대상을 target이라고 한다. 

 

-operation() : 구현한 코드를 보면 cacheValue에 값이 ㅇ벗으면 실제 객체(target)를 호출해서 값을 구한다. 그리고 구한 값을 cacheValue에 저장하고 반환한다. 만약 cacheValue에 값이 있으면 실제 객체를 전혀 호출하지 않고, 캐시 값을 그대로 반환한다. 따라서 처음 조회 이후에는 캐시에서 매우 빠르게 데이터를 조회할 수 있다. 

 

7. 테스트코드 작성

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

realSubject 와 cacheProxy 를 생성하고 둘을 연결한다. 결과적으로 cacheProxy 가 realSubject 를 참조하는 런타임 객체 의존관계가 완성된다. 그리고 마지막으로 client 에 realSubject 가 아닌 cacheProxy 를 주입한다.

 

이 과정을 통해서 client -> cacheProxy -> realSubject 런타임 객체 의존 관계가 완성된다. cacheProxyTest() 는 client.execute() 을 총 3번 호출한다. 이번에는 클라이언트가 실제 realSubject 를 호출하는 것이 아니라 cacheProxy 를 호출하게 된다.

 

 

정리

이렇게 프록시 패턴에 대해 알아보았다. 하지만 이 패턴 어디선가 많이 보던 방식이다. 프록시를 적용하기 전 의존관계에서 실제 객체를 반환하고 프록시를 적용한 다음에는 실제 객체를 주입받아 꾸며주는 방식

 

그렇다 바로 데코레이터 패턴이랑 똑같다. 

 

프록시를 통해서 할 수 있는 일은 크게 두가지로 구분할 수 있다.

 

1. 접근 제어

-권한에 따른 접근 차단

-캐싱

-지연 로딩

2. 부가 기능 추가

-원래 서버가 제공하는 기능에 더해서 부가 기능ㅇ르 수행한다. 

-ex) 실행 시간을 측정해서 추가 로그를 남긴다. 

 

GOF디자인 패턴에서는 프록시 패턴과 데코레이터 패턴 모두 프록시를 사용하기 때문에 둘을 구분하는 방법으로 의도를 선택했다. 

 

즉 접근 제어가 목적이라면 프록시 패턴으로 부르고, 새로운 기능 추가가 목적이라면 데코레이터 패턴이라고 부르는 것이다. 

 

둘 다 프록시를 사용하지만, 의도가 다르다는 점이 핵심이다. 용어가 프록시 패턴이라고 해서 이 패턴만 프록시를 사용하는 것은 아니다. 데코레이터 패턴도 프록시를 사용한다. 

 

 

'리팩토링 > GOF 디자인패턴' 카테고리의 다른 글

데코레이터 패턴  (0) 2022.07.07
전략 패턴  (0) 2022.07.06
템플릿 메서드 패턴  (0) 2022.07.05