Design Pattern

[디자인 패턴] 프록시 패턴과 데코레이터 패턴

꾸준함. 2021. 12. 1. 15:06

개요

이번 게시글에서는 템플릿 메서드 패턴 및 콜백 패턴(https://jaimemin.tistory.com/2014)에 이어 프록시 패턴과 데코레이터 패턴에 대해 간단히 정리해보겠습니다.

우선, 간단하게 프록시가 무엇인지 설명하고 이어서 프록시 패턴과 데코레이터 페턴에 대해 설명해보겠습니다.

 

1.  프록시

기본적으로 클라이언트는 서버에 필요한 것을 요청하고, 서버는 클라이언트의 요청을 처리합니다.

예를 들어, 컴퓨터 네트워크에서는 웹 브라우저가 서버에 요청을 하고 서버는 클라이언트에서 받은 요청을 처리합니다.

해당 개념을 객체에도 도입시킬 수 있는데 이때 요청하는 객체는 클라이언트가 되고, 요청을 처리하는 객체는 서버가 됩니다.

클라이언트 요청을 서버에 직접 할 수도 있지만 대리자를 통해서 대신 간접적으로 서버에 요청할 수도 있는데 이때 대리자를 영어로 Proxy라고 합니다.

 

https://en.wikipedia.org/wiki/Proxy_server

 

위 그림처럼 프록시는 하나일 수도 있지만 클라이언트의 요청을 여러 프록시를 거친 후 서버에 요청하는 프록시 체인을 구성할 수도 있습니다.

여태까지의 설명만 들어보면 아무 객체나 프록시 역할을 할 수 있을 것 같지만 프록시는 서버와 같은 인터페이스를 사용해야 한다는 조건이 있습니다.

즉, 클라이언트 입장에서 보면 서버에 요청하는지 프록시에 요청하는지 몰라야 하며 어느 케이스더라도 동일하게 요청을 처리해야 합니다.

 

1.1 프록시의 주요 기능

프록시의 주요 기능은 아래와 같이 크게 2가지로 구분할 수 있습니다.

 

A. 접근 제어

  • 캐싱
  • Lazy Loading
  • 권한에 따른 접근 차단

B. 부가 기능 추가

  • 원래 서버가 제공하는 기능에 추가적으로 부가 기능을 수행
  • ex) 메서드 동작 시간을 추가 로그로 남기는 기능 추가

 

2. 프록시 패턴과 데코레이터 패턴

프록시 패턴과 데코레이터 패턴 모두 프록시를 사용하는 방법이기 때문에 형태는 유사하나 GOF 디자인 패턴에서는 아래와 같이 이 둘을 의도에 따라 구분합니다.

  • 프록시 패턴: 접근 제어가 목적
  • 데코레이터 패턴: 부가 기능 추가가 목적

 

2.1 프록시 패턴

프록시 패턴은 기존에 언급했드시 접근 제어를 목적으로 설계된 디자인 패턴입니다.

해당 디자인 패턴은 캐싱을 위해 자주 사용되는데 호출될 때마다 동일한 데이터를 반환하지만 시스템에 큰 부하를 줄 경우 사용할 수 있는 디자인 패턴입니다.

예시는 아래와 같습니다.

  • 한번 호출될 때마다 2초 후 동일한 데이터 반환

 

프록시 패턴 적용 전

 

 

* 매번 호출할 때마다 2초가 걸리므로 비효율적

 

프록시 패턴 적용 후

 

 

 

* 캐싱이 되므로 효율적

 

2.2 데코레이터 패턴

데코레이터 패턴은 프록시 패턴과 달리 기존 서버가 제공하는 기능에 부가 기능 추가를 목적으로 설계된 디자인 패턴입니다.

대표적인 예시로는 요청 값이나 응답 값을 중간에 변형하거나 메서드 수행 시간을 출력해주는 기능을 추가하는 프록시들입니다.

기존에 클라이언트와 서버 사이에 둘 이상의 프록시가 체인 형태로 존재할 수 있다고 명시하였으므로 이번 예시에서는 응답값을 변형하는 프록시 하나, 메서드 수행시간을 출력해주는 프록시 하나를 추가해보겠습니다.

 

데코레이터 패턴 예시


 

* 위 예시의 전체적인 흐름을 보면 client -> TimeDecorator -> MessageDecorator -> ExampleComponent의 객체 의존 관계를 형성하고 실행되는 것을 확인할 수 있습니다.

* TimeDecorator가 MessageDecorator를 실행하고 실행시간을 측정하며 MessageDecorator는 메시지를 변형해준 뒤 서버에 결괏값을 반환합니다.

* 이처럼 여러 프록시를 체인 형태로 구성할 수 있습니다.

 

3. 인터페이스 기반 프록시 vs 구현 클래스 기반 프록시

기존에 작성한 프록시 패턴 예시와 데코레이터 페턴 예시는 모두 인터페이스 기반으로 개발된 프록시입니다.

인터페이스 기반의 경우 프록시와 DI 덕분에 원본 소스를 전혀 건들이지 않고 접근 권한 제어 및 부가 기능 추가를 할 수 있는 장점이 있지만

 프록시 클래스를 매번 생성하기 때문에 관리 포인트가 늘어나는 것이 단점입니다.

반면, 구현 클래스 기반으로 개발된 프록시의 경우 인터페이스가 없이 상속을 통해 구체 클래스에 바로 적용되므로 구현을 변경할 가능성이 없는 코드에 적용할 경우 인터페이스를 사용하는 것보다 실용적입니다.

한편, 하지만 클래스 기반 프록시의 경우 상속을 받기 때문에 필요 없는 부모 생성자를 호출해야 하는 단점이 있습니다. 이를 위해 생성자에 super(null)을 호출해야하는 번거로움이 있습니다. super(null)을 호출하지 않고 별다른 super를 호출하지 않을 경우 디폴트 생성자가 호출되면서 에러가 발생할 수 있습니다. 또한, 메서드에 final 키워드가 붙으면 해당 메서드를 overriding 못한다는 단점도 있습니다.

 

구현 클래스 기반 프록시 예시


 

정리

위 예시들을 보면 프록시 패턴과 데코레이터 패턴이 매우 유사한 것을 확인할 수 있습니다.

이 둘을 구분하기 위해서는 위에서도 언급했다시피 해당 패턴을 생성한 의도를 파악해야 합니다.

 

프록시 패턴

  • 접근 제어를 위한 목적

데코레이터 패턴

  • 원본 서버의 기능에 부가 기능을 추가하는 목적

 

인터페이스 기반 프록시와 구현 클래스 기반 프록시를 비교하면 인터페이스 기반 프록시가 훨씬 장점이 많고 이론적으로 유연한 방법입니다. 하지만, 인터페이스가 반드시 필요하지 않은 케이스도 있으므로 이를 확인하고 선택하는 것이 좋을 것 같습니다.

 

인터페이스 기반 프록시

  • 인터페이스만 같을 경우 모든 곳에 적용 가능
  • 상속이라는 제약에서 자유로움
  • 역할과 구현을 명확하게 나눌 수 있음
  • 로직이 같은데도 적용해야 할 대상 클래스에 1:1 매칭 해서 프록시 클래스를 매번 생성하기 때문에 관리 포인트가 늘어남
  • 캐스팅 관련 단점 존재

구현 클래스 기반 프록시

  • 구현을 변경할 일이 없는 클래스의 경우 실용적
  • 상속에 따른 제약 존재
    • 부모 클래스의 생성자를 호출
    • 클래스에 final 키워드 붙을 경우 상속 불가
    • 메서드에 final 키워드가 붙을 경우 overriding X
  • 유연하지 못한 설계

 

참고

인프런 스프링 핵심 원리 - 고급편 (김영한 강사님)

 

 

반응형