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

프록시란 '대리자'란 의미를 가지고 있다. 클라이언트가 서버에게 요청할때, 프록시를 통해서 간접적으로 요청할 수 있다.
프록시와 서버는 같은 인터페이스를 구현하거나, 프록시가 서버를 상속하여, 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;
}
}
MessageDecorator 는 Component 인터페이스를 구현한다. 프록시가 호출해야 하는 대상을 component 에 저장한다.
operation() 을 호출하면 프록시와 연결된 대상을 호출( component.operation()) 하고, 그 응답 값에
***** 을 더해서 꾸며준 다음 반환한다.
@Test
void decorator1() {
Component component = new RealComponent();
Component messageDecorator = new MessageDecorator(component);
DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
client.execute();
}
프록시 패턴 vs 데코레이터 패턴
프록시를 사용하고 해당 프록시가 접근 제어가 목적이라면 프록시 패턴이고,
새로운 기능을 추가하는 것이 목적이라면 데코레이터 패턴이 된다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 객체를 생성하는 방법 (생성자, 정적메서드, 빌더패턴) (0) | 2024.06.05 |
|---|---|
| 프록시 적용하기 (로그 추적기) (0) | 2024.05.13 |
| 전략패턴과 콜백패턴의 적용 (0) | 2024.05.10 |
| 템플릿 메서드 패턴과 적용 (0) | 2024.05.10 |
| [java] 싱글톤 패턴 (0) | 2023.05.04 |
