본문 바로가기
Design Pattern

프록시 패턴 & 데코레이터 패턴

by 권성호 2022. 4. 18.

사실이 아니라 공부한 내용과 생각을 정리한 글입니다. 언제든 가르침을 주신다면 감사하겠습니다.

 

해당 글은 김영한 님의 인프런 강의 스프링 핵심 원리 - 고급 편 을 학습하며 정리한 내용입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

지난 글에서 전략 패턴과 한계점에 대해 정리했습니다.

(https://ksh-dev.tistory.com/62)

이번 글에서는 전략 패턴의 한계를 극복하기 위한 또 다른 패턴인 프락시 패턴 & 데코레이터 패턴에 대해 알아보겠습니다.

 

1.  해결하고자 하는 문제 

기존 핵심 비즈니스 로직을 전혀 수정하지 않고 부가 기능을 추가하고 싶습니다.

 

2.  문제를 푸는 방식

프록시 패턴 & 데코레이터 패턴 클래스 의존관계

그림만 보면 Context 가 Strategy 인터페이스 타입을 참조하고 있고 DI를 통해 의존관계를 조립 후 사용한다는 면에서 전략 패턴과 매우 유사한 형태임을 알 수 있습니다.

 

그런데, 여기서 가장 큰 차이는 Proxy는 Server 클래스를 참조하고 있다는 점 입니다.

즉, 런타임 의존관계는 아래와 같습니다.

프록시 패턴 & 데코레이터 패턴 런타임 의존관계

런타임 의존관계를 보면 전략 패턴과의 차이를 확실히 알 수 있습니다.

프락시 패턴의 경우 런타임에 client 가 바라보는 구현채는 proxy이고 proxy는 내부적으로 server 객체를 참조하는 형태를 갖습니다.

여기서 핵심은 client는 proxy를 참조하는지 server를 참조하는지 전혀 몰라야 한다는 점입니다.

Java에서는 이를 상속을 통해 구현할 수 있습니다.

인터페이스를 사용하던가 클래스 자체를 상속한 proxy를 만들 수 있습니다.

 

proxy를 사용하면 client와 server 사이의 부가적인 작업을 proxy를 통해 수행할 수 있습니다.

일반적으로 프락시를 통해 접근 제어(권한에 따른 접근 차단 & 캐싱 & 지연 로딩)를 달성한다면 이는 프록시 패턴으로 불리고, 부가 기능 추가를 달성한다면 데코레이터 패턴으로 불립니다.

 

3.  예제 코드를 통한 이해

최초에 순수 비즈니스 로직이 아래와 같이 있었다고 가정합니다.

@Slf4j
public class PureLogic {
    public void logic(){
        log.info("순수 비즈니스 로직");
    }
}

여기에 프록시를 적용해 실행시간을 로깅하는 부가 기능을 기존 순수 비즈니스 로직의 변경 없이 추가해 보겠습니다.

우선 PureLogic을 상속받아 아래와 같이 구현합니다.

@Slf4j
public class PureLogicProxy extends PureLogic{
    private final PureLogic target;

    public PureLogicProxy(PureLogic target){
        super();
        this.target = target;
    }

    public void logic(){
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        target.logic(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

 

이제 클라이언트 코드는 아래와 같습니다.

public class Client {
    private final PureLogic pureLogic;

    public Client(PureLogic pureLogic){
        this.pureLogic = pureLogic;
    }

    public void doLogic(){
        pureLogic.logic();
    }
}

클라이언트 클래스는 PureLogic 클래스만 알고 있고 Proxy는 전혀 모릅니다. 

그리고 PureLogic 구현채를 생성자를 통해 외부에서 주입받고 있습니다.

 

클라이언트 테스트 코드는 다음과 같습니다.

public class ClientTest {
    @Test
    public void clientTest(){
        Client client = new Client(new PureLogicProxy(new PureLogic()));
        client.doLogic();
    }
}

Client 클래스를 통해 객체 생성 시 DI를 통해 PureLogic의 Proxy 객체를 주입하고 있습니다.

이런 과정을 통해 기존 PureLogic 클래스의 코드를 전혀 변경하지 않고 부가 기능을 확장할 수 있었습니다.

 

4. 한계점

프락시를 사용하기 위해 기존 모든 클래스로 부터 상속이나 인터페이스를 통해 프록시를 만들고 적용 가능한 구조로 변경해야 합니다.

기존 클래스가 천 개라면 프록시 클래스를 천개 만들어야 하는 상황입니다.

메서드 실행시간을 로깅하기 위한 코드는 모두 동일한데 천 개의 프락시를 만들고 천개의 로깅 코드를 작성해야 합니다.

프록시 클래스를 하나만 만들고 모든 곳에 적용할 수 있는 방법이 없을까요?

다음 글에서는 동적 프락시에 대해 학습합니다.

 

 

 

'Design Pattern' 카테고리의 다른 글

전략 패턴  (0) 2022.04.18
템플릿 메소드 패턴  (0) 2022.04.18

댓글