디자인 패턴 - 프록시 패턴(Proxy Pattern), 데코레이터 패턴(Decorator Pattern)

SeungTaek·2021년 10월 31일
0
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!

📒 프록시란?

  • Client에서 Server을 직접 호출하고, 처리 결과를 직접 받는다. 이것을 직접 호출이라 한다.
    • Client -> Server
  • Client에서 Server을 직접 호출하는 것이 아니라 어떤 대리자를 통해서 대신 간접적으로 서버에 요청할 수도 있다. 이것을 간접 호출이라 한다.
    • 여기서 간접 호출하는 대상을 프록시(Proxy)라 한다.
    • Client -> Proxy -> Server

  • 프록시는 Client와 Server의 중간에 있기 때문에 여러가지 일을 수행 할 수 있다.
    • 권한에 따른 접근 차단, 캐싱, 지연로딩을 수행하는 접근 제어
    • 서버의 기능에 다른 기능까지 추가해주는 부가 기능 추가 (요청, 응답값을 변형해주거나 로그 기록 등)
    • 대리자가 또 다른 대리자를 호출하는 프록시 체인

📌 프록시는 대체 가능해야 한다.

  • 아무 객체나 프록시가 되는것은 아니다.
  • 클라이언트 입장에서는 서버에 요청을 한건지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.
  • 즉, 서버와 프록시는 같은 인터페이스를 사용해야 하며, 클라이언트의 코드를 하나도 건들이지 않고 프록시 추가와 런타임 객체 의존 관계 주입만 변경해서 사용할 수 있어야 한다.
  • Client는 ServerInterface를 의존해야 한다. 그리고 ServerInterface의 구현체로 실제 서버와 Proxy가 존재한다.
  • 따라서 DI를 사용해서 대체 가능하다.

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

  • 두 패턴 모두 프록시를 사용하는 방법이다.
  • 하지만 GOF 디자인 패턴에서는 이 둘을 의도(Intent)에 따라서 구분한다.
    • 프록시 패턴: 접근 제어가 목적
    • 데코레이터 패턴: 새로운 기능 추가가 목적



📒 프록시 패턴 예제

🎈 Subject 인터페이스

public interface Subject {
	String operation();
}

🎈 RealSubject(실제 서버) 구현 클래스

@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();
        }
    }
}
  • sleep(1000)은 쓰레드가 1초를 쉬는 코드인데, 서버에서 1초 동안 걸리는 무거운 작업이라는 것을 가정한다.

🎈 ProxyPatternClient

public class ProxyPatternClient {
    private Subject subject;

    public ProxyPatternClient(Subject subject) {
        this.subject = subject;
    }

    public void execute(){
        subject.operation();
    }
}

🎈 실행

void noProxyTest() {
	RealSubject realSubject = new RealSubject();
	ProxyPatternClient client = new ProxyPatternClient(realSubject);
	client.execute();
	client.execute();
	client.execute();
}

//실행 결과
RealSubject - 실제 객체 호출
RealSubject - 실제 객체 호출
RealSubject - 실제 객체 호출
  • 너무 당연한 결과이다.
  • 총 수행시간은 3초이다.
    • 변하지 않는 데이터라면 어디에 보관해두고 캐싱하는게 성능상 좋지 않을까?
    • 프록시 패턴의 주요 기능인 접근 제어를 사용해보자.

🎈 CacheProxy

@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;
    }
}
  • 실제 서버와 동일하게 Subject를 구현한다.
  • 프록시는 실제 서버를 호출해야 한다. 따라서 실제 객체의 참조를 가지고 있어야 하는데, 이렇게 프록시가 호출하는 대상을 target이라 한다.
  • operation()은 내부에 저장된 값이 있으면 바로 리턴(캐싱), 없으면 실제 서버를 호출해 리턴해주는 로직이다.

🎈 실행

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

//실행 결과
CacheProxy - 프록시 호출
RealSubject - 실제 객체 호출
CacheProxy - 프록시 호출
CacheProxy - 프록시 호출
  • 클라이언트의 코드를 하나도 수정하지 않고 프록시 패턴을 적용했다.
  • 캐싱을 위해 실제 서버는 단 1번만 호출했고, 그 외의 호출은 모두 프록시에서 즉시 리턴했다.



📒 데코레이터 패턴

  • 위에서 작성한 코드를 조금 수정해서 데코레이터 패턴과 프록시 패턴을 함께 사용하는 프록시 체인을 만들어보자.
  • Client -> MessageDecorator -> CacheProxy -> Server
@Slf4j
public class MessageDecorator implements Subject {
	private Subject subject;
	public MessageDecorator(Subject subject) {
		this.subject = subject;
	}
    
	@Override
	public String operation() {
		log.info("MessageDecorator 실행");
		String result = subject.operation();
		String decoResult = "*****" + result + "*****";
		log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result, decoResult);
		return decoResult;
	}
}

🎈 실행

void decorator() {
	Subject realSubject = new RealSubject();
	Subject cacheProxy = new CacheProxy(realSubject);
	Subject MessageDecorator = new MessageDecorator(cacheProxy);
	ProxyPatternClient client = new ProxyPatternClient(MessageDecorator);
	client.execute();
}

//실행 결과
MessageDecorator - MessageDecorator 실행
CacheProxy - 프록시 호출
RealSubject - 실제 객체 호출
MessageDecorator - MessageDecorator 꾸미기 적용 전=data, 적용 후=*****data*****
  • 데코레이터 패턴과 프록시 체인이 되는 것을 확인할 수 있다.

인프런의 '스프링 핵심 원리 고급편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.

profile
I Think So!

0개의 댓글