백엔드/Spring

[Spring] Proxy & Decorator Pattern

kwang2134 2024. 11. 2. 15:02
728x90
반응형
728x90

Proxy Pattern

  • 다른 객체에 대한 접근을 제어하는 대리 객체를 만드는 패턴
  • 실제 객체에 대한 접근을 간접적으로 처리
  • 주로 접근 제어나 지연 로딩등의 목적을 가지고 사용

구성 요소

  • Subject: 프록시가 제어할 실제 객체가 구현해야 하는 인터페이스
  • Real Subject: 실제 비즈니스 로직을 구현한 클래스 -> 실제 작업 수행
  • Proxy: 주제 인터페이스를 구현, Real Subject에 대한 참조를 가짐 -> 클라이언트의 요청을 Real Subject에 전달

동작 과정

  1. 클라이언트 요청
    • 클라이언트는 실제 객체가 아닌 프록시 객체에 요청
    • 실제 객체와 동일한 인터페이스를 구현하여 실제 객체를 호출하는 것처럼 보임
  2. 프록시 객체 처리
    • 프록시 객체는 클라이언트로부터 받은 요청을 처리
    • 접근 제어 용도로 사용 시 실제 객체에 접근 권한이 있는지 확인하는 등 처리 과정 추가 가능
  3. Real Subject 호출
    • 프록시 객체에서 처리 후 실제 객체 호출
    • 프록시 객체는 실제 Real Subject 객체를 참조로 가지고 있어 메서드 호출 가능 -> 실제 객체 사용 시 객체를 생성하는 지연 초기화 (lazy initialization)
    • 실제 객체가 아닌 체인 형식으로 다른 프록시 호출 가능 -> 같은 인터페이스를 구현한 객체이기 때문
  4. Real Subject 작업 수행
    • 클라이언트 요청에 대한 실제 작업을 처리
  5. 결과 반환
    • Real Subject를 통해 처리한 결과를 프록시가 받아 클라이언트에게 반환

Proxy Pattern 요청 흐름

//Subject - 실제 객체가 구현할 인터페이스
public interface Subject {
    void request();
}

//RealSubject - 실제 객체
public class RealSubject implements Subject {
    @Override
    public void request() {
       log.info("실제 객체 호출");
    }
}

//Proxy - 프록시 객체 (실제 객체와 같은 Subject 인터페이스를 구현)
public class Proxy implements Subject {
    private RealSubject realSubject;	//실제 객체의 참조를 가짐

    public Proxy() {
        this.realSubject = new RealSubject();
    }

    @Override
    public void request() {
        log.info("프록시: 접근 권한 체크");
        realSubject.request();
    }
}

//Client - 요청을 보낼 클라이언트
public class Client {

    private final Subject subject;

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

//사용
RealSubject realSubject = new RealSubject();
Client client = new Client(realSubject);
client.execute();

장점

  • 실제 객체에 대한 접근을 통제할 수 있어 보안을 강화
  • 객체를 필요할 때만 생성하거나 초기화하여 메모리와 성능을 최적화

단점

  • 추가적인 레이어가 생기므로 코드가 복잡해짐 -> 프록시 객체를 생성해야 함
  • 프록시를 통해 요청을 처리하기 때문에 약간의 성능 저하가 발생가능 -> 캐싱 등으로 사용 시 성능 향상 가능

Decorator Pattern

  • 프록시 패턴과 유사하게 실제 객체를 감싼 데코레이터 객체를 통해 요청과 반환을 받음
  • 프록시 패턴은 접근 제어를 주목적으로 사용했다면 데코레이터 패턴은 기능을 동적으로 추가하거나 수정하기 위한 목적으로 사용

구성 요소

  • Component: 기능을 제공하는 인터페이스 또는 추상 클래스 (Subject)
  • Concrete Component: 컴포넌트 인터페이스를 구현하는 구체적인 클래스 (Real Subject) - 기본 기능 제공
  • Decorator: 컴포넌트 인터페이스를 구현하며 컴포넌트를 포함하는 클래스 (Proxy) - 기존 기능을 감싸고 추가적인 기능 제공
  • Concrete Decorator: 데코레이터 클래스를 상속받아 특정 기능을 추가하는 클래스

동작 과정

  1. 기본 객체 생성 
    • 클라이언트는 기본 기능을 제공하는 객체를 생성
    • 기능을 추가하기 전 핵심 로직 -> Real Subject 같은 객체
  2. 데코레이터 객체 생성
    • 원하는 기능을 추가하기 위해 데코레이터 객체 생성 -> 프록시 객체 생성
    • 데코레이터는 기존 리얼 객체를 인자로 받아 내부에서 참조 -> 프록시 내부에 실제 객체 참조를 가지는 것과 동일
  3. 기능 조합 및 추가
    • 여러 데코레이터를 조합하여 다양한 기능을 추가할 수 있음
    • 각 데코레이터는 자신의 기능을 추가하기 위해 내부의 리얼 객체에 대한 호출을 감싸는 구조를 가짐
  4. 메서드 호출
    • 데코레이터 전처리: 실제 객체의 메서드 호출 전 추가 기능 실행
    • 실제 객체 호출: 실제 객체의 메서드 호출 - 혹은 다음 데코레이터 호출
    • 데코레이터 후처리: 메서드 호출 후 추가 기능 실행
  5. 결과 반환
    • 클라이언트에게 결과 반환

Decorator Pattern 요청 흐름

//Component -  실제 객체가 구현할 인터페이스
public interface Component {
    String request();  
}

//Concrete Component - 실제 객체 
public class ConcreteComponent implements Component {
    @Override
    public String request() {
        return "example";
    }
}

체인 형태의 호출을 이용한 방식

//Decorator - 시간을 측정하는 기능이 추가된 데코레이터
public class TimeDecorator implements Component {

    private final Component component;

    public TimeDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String request() {
    	log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();

        String result = component.request();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeDecorator 종료 resultTime={}ms", resultTime);
        return result;  
    }
}

//Decorator - Message 포매팅이 추가된 데코레이터
public class MessageDecorator implements Component{

    private final Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }
    
    @Override
    public String request() {
        log.info("MessageDecorator 실행");

        String request = component.request();
        String decoResult = "*****" + request + "*****";
        log.info("MessageDecorator 적용 전={}, 적용 후={}", request, decoResult);

        return decoResult;
    }
}

//Client
public class Client {

    private final Component component;

    public void execute() {
        String result = component.request();
        log.info("result={}",result);
    }
}

//사용
RealComponent realComponent = new RealComponent();
MessageDecorator messageDecorator = new MessageDecorator(realComponent);
TimeDecorator timeDecorator = new TimeDecorator(messageDecorator);
Client client = new Client(timeDecorator);
client.execute();

 

상속을 이용한 방식

//Decorator - 기본 데코레이터
public abstract class Decorator implements Component {

    protected final Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public String request() {
        String result = component.request();
        return result;  
    }
}


//Concrete Decorator - 시간을 측정하는 기능이 추가된 데코레이터
public class TimeDecorator extends Decorator {
    
    public TimeDecorator(Component component){
        super(component);
    }
    
    @Override
    public String request() {
    	log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();

        String result = component.request();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeDecorator 종료 resultTime={}ms", resultTime);
        return result;  
    }
}

//Concrete Decorator - Message 포매팅이 추가된 데코레이터
public class MessageDecorator extends Decorator{

    public MessageDecorator(Component component){
        super(component);
    }
    
    @Override
    public String request() {
        log.info("MessageDecorator 실행");

        String request = component.request();
        String decoResult = "*****" + request + "*****";
        log.info("MessageDecorator 적용 전={}, 적용 후={}", request, decoResult);

        return decoResult;
    }
}

//Client
public class Client {

    private final Component component;

    public void execute() {
        String result = component.request();
        log.info("result={}",result);
    }
}

//사용
RealComponent realComponent = new RealComponent();
MessageDecorator messageDecorator = new MessageDecorator(realComponent);
TimeDecorator timeDecorator = new TimeDecorator(realComponent);

Client timeClient = new Client(timeDecorator);		//시간을 측정하는 데코레이터만 사용
Client messageClient = new Client(messageDecorator);	//메세지 포매팅을 위한 데코레이터만 사용

timeClient.execute();
messageClient.execute();

장점

  • 기존 코드를 변경하지 않고 기능을 추가할 수 있음
  • 기존 객체에 새로운 기능을 추가하는 데 필요한 코드를 중복하지 않고 재사용할 수 있음
  • 단순한 인터페이스로 기능을 추가 가능

단점

  • 여러 개의 데코레이터를 사용하면 코드의 구조가 복잡해짐
  • 데코레이터가 여러 개 중첩되면 호출 스택이 깊어져 성능이 저하될 수 있음

정리 - 사용 목적

  • Proxy: 접근 제어, 지연 로딩 등의 목적으로 사용
  • Decorator: 동적 기능 추가 목적으로 사용

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런 강의 내용 참고

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] ProxyFactory  (1) 2024.11.06
[Spring] 동적 프록시  (0) 2024.11.05
[Spring] Strategy & Template Callback  (0) 2024.11.01
[Spring] Template Method  (0) 2024.10.31
[Spring] Thread Pool & ThreadLocal  (0) 2024.10.30