두 패턴 모두 프록시를 활용한 디자인 패턴이다. 디자인 패턴은 구분하는 기준은 '의도'에 의해 구분되기 때문에 비슷하게 구현되는 패턴들이 많다. 이 두 패턴도 그러한 유형이다.
그럼 프록시 패턴과, 데이코레이터의 패턴의 의도를 알아보기 전에 프록시란 무엇인지 부터 알아보자.
프록시, Proxy
그대로 번역하면 '대리자'라는 뜻을 가진다. 웹은 기본적으로 Client -> Server 로 호출을 한다. 프록시를 활용하게 되면 Client -> Proxy -> Server로 중간에 대리자를 끼게 된다. 중간에는 꼭 하나의 프록시만 존재할 수 있는 것은 아니다. 경우에 따라 여러 개의 프록시가 존재할 수 있다.
그렇다면 프록시가 될 수 있는 조건은 무엇일까?
조건
객체에서 프록시가 되려면, 클라이언트는 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.
쉽게 이야기해서 서버와 프록시는 같은 인터페이스를 사용해야 한다. 그리고 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있어야 한다.

그럼 이렇게까지 해서 프록시를 사용하는 이유도 있을 것이다.
기능
크게 '접근 제어'와 '부가 기능 추가'로 나뉜다.
- 접근 제어
- 권한에 따른 접근 차단
- 캐싱
- 지연 로딩
- 부가 기능 추가
- 원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다.
- 예) 요청 값이나, 응답 값을 중간에 변형한다.
- 예) 실행 시간을 측정해서 추가 로그를 남긴다.
이러한 기능 즉, 의도에 따라서 프록시 패턴과 디자인 패턴으로 나뉘게 된다.
프록시 패턴 VS 디자인 패턴
- 프록시 패턴 - 접근 제어가 목적
- 디자인 패턴 - 부가 기능 추가가 목적
프록시 패턴 적용
실제 호출을 담당하는 Subject
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();
}
}
}
Subject를 받아와 접근을 제어 하는 프록시
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;
}
}
이러한 프록시를 실질적으로 호출하는 client
public class ProxyPatternClient {
private Subject subject;
public ProxyPatternClient(Subject subject) {
this.subject = subject;
}
public void execute() {
subject.operation();
}
}
전체적인 구성은 위와 같고 이를 테스트 하기 위한 코드를 돌려보면
@Test
void cacheProxyTest() {
Subject realSubject = new RealSubject();
Subject cacheProxy = new CacheProxy(realSubject);
ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
client.execute();
client.execute();
client.execute();
}
RealSubject의 operation은 처음 한 번 1번만 호출되고 나머지는 프록시를 통해 호출되는 것을 확인할 수 있다.
데코레이터 패턴 적용
큰 틀은 똑같다. 프록시는 Subject를 통해 실제 연산을 구분하는 용도로 썼다면 여기선 부가 기능을 추가하기 때문에 요소를 추가한다는 의미에서 Component를 사용한다.
public class RealComponent implements Component {
@Override
public String operation() {
log.info("RealComponent 실행");
return "data";
}
}
위 operation을 통해 받은 data에 대해 문자열을 덧붙이는 MessageDecorator
public class MessageDecorator implements Component {
private Component component;
public MessageDecorator(Component component) {
this.component = component;
}
@Override
public String operation() {
log.info("MessageDecorator 실행");
String result = component.operation();
String decoResult = "*****" + result + "*****";
log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result,
decoResult);
return decoResult;
}
}
함수 호출 시간을 출력하는 TimeDecorator
public class TimeDecorator implements Component {
private Component component;
public TimeDecorator(Component component) {
this.component = component;
}
@Override
public String operation() {
log.info("TimeDecorator 실행");
long startTime = System.currentTimeMillis();
String result = component.operation();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeDecorator 종료 resultTime={}ms", resultTime);
return result;
}
}
테스트 코드도 비슷하다.
@Test
void decorator2() {
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
Component timeDecorator = new TimeDecorator(messageDecorator);
DecoratorPatternClient client = new DecoratorPatternClient(timeDecorator);
client.execute();
}
'Study > OOP' 카테고리의 다른 글
IoC, DI, 그리고 컨테이너 (0) | 2023.07.25 |
---|---|
생성자 주입을 통한 OOP (0) | 2023.07.25 |
객체 지향 특징과 SOLID 원칙 (0) | 2023.07.25 |