데코레이션 패턴 vs 프록시 패턴

프록시란?

  • 클라이언트에서 서버를 직접 호출하고, 처리결과를 받는다. -> 직접호출
  • 클라이언트에서 서버를 직접 호출하지 않고, 대리자를 통해 간접적으로 서버에 요청 -> 간접호출

여기서, 간접호출하는 대상을 프록시 라고 한다.

프록시는 클라이언트와 서버의 중간에 위치하기 때문에, 여러가지 일을 수행할 수 있다.

    1. 권한에 따른 접근 차단, 지연로딩을 수행하는 접근 제어
    1. 서버의 기능에 다른 기능을 추가해주는 부가 기능 추가 ex) 로그, 가공
    1. 대리자가 또 다른 대리자를 호출하는 프록시 체인

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

    1. 아무 객체나 프록시가 되는것은 아니다.
    1. 클라이언트는 서버에 요청한지, 프록시에게 요청한지 몰라야한다.
  • 즉, 클라이언트의 코드를 건드리지 않고 프록시 추가와 런타임 객체 의존 관계 주입만 변경하여야 한다.

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

  • 두 패턴 모두 프록시를 사용하는 방법이다.
  • 또한, 둘 모두 원본 객체를 건드리지 않고, 추가 기능을 실행 할 수 있다.
  • 하지만 GOF 디자인 패턴에서는 이 둘을 의도(Intent)에 따라서 구분한다. 프록시 패턴 : 접근 제어가 목적 데코레이터 패턴 : 새로운 기능 추가가 목적
기준 데코레이터 패턴 프록시 패턴
목적 객체에 동적으로 새로운 기능을 추가하기 위함 다른 객체에 대한 접근을 제어하거나 부가기능을 제공하기 위함
사용 시점 실행 시간에 객체의 기능을 확장하고자 할 때 객체의 생성이나 접근에 대한 제어가 필요할 때
구현 방식 추상 클래스나 인터페이스를 사용하여 객체에 새로운 기능을 추가 프록시 객체를 통해 실제 객체에 접근, 프록시 객체와 실제 객체는 같은 인터페이스를 구현

데코레이터 패턴

1.객체들이 사용하는 코드를 훼손하지 않으면서 런타임에 추가 행동들을 객체들에 할당할 때 사용.

  • 상속을 사용하여 객체의 행동을 호가장하는 것이 어색하거나, 불가능할 때 사용할 수 있다. -만일 final 키워드가 기입된 클래스의 경우는 데코레이터 패턴을 통해 래핑하여 재사용 할 수 있다.

ex) 택스트 편집기 - 택스트 편집기에서 굵게, 이텔릭체, 밑줄 등과 같은 다양한 텍스트 포맷을 지원한다. Spring - HttpServletRequestWrapper : Sevlet에서 제공하는 Wrapper로 데코레이터 패턴을 지원한다.

프록시 패턴

  1. 가상 프록시, 지연 로딩이 필요한 경우

    • 부담되는 서비스 객체를 바로 초기화 한다면 리소스 낭비가 발생 할 수 있으므로, 프록시 객체를 통해 객체를 초기화 할 수 있다.
  2. 보호 프록시, 접근 제어가 필요한 경우

    • 특정 클라이언트에 대해서만 서비스 객체를 이용할 수 있도록 하려는 경우 프록시 객체를 통해서 처리할 수 있다.
  3. 원격 프록시, 원격 서비스의 로컬 실행이 필요한 경우

    • 서비스 객체가 원격 서버에 있는 경우에는 네트워크를 통해 클라이언트의 요청을 전달하여 처리할 수 있다.
  4. 로깅 프록시, 서비스 객체에 대한 로깅이 필요한 경우

    • 프록시 객체에서 서비스에 전달하기 전과 후로 로깅을 진행할 수 있다.
  5. 캐싱 프록시, 요청 결과를 캐시하고 생명주기를 관리해야 하는 경우

ex) Spring JAP

  • JPA의 지연 로딩의 경우, 가상 프록시를 적용하여 실제로 객체를 조회하기 이전까지 프록시 객체로 Entity를 대신하여 제공한다. ex) Spring AOP
  • Spring AOP는 프록시 패턴을 사용하여 트렌젝션 관리, 로깅, 보안 등의 작업을 프록시에서 처리한다.

public void write(char cbuf[], int off, int len) throws IOException {
    synchronized (lock) {
        ensureOpen();
        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
            ((off + len) > cbuf.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }

        if (len >= nChars) {
            /* If the request length exceeds the size of the output buffer,
                flush the buffer and then write the data directly.  In this
                way buffered streams will cascade harmlessly. */
            flushBuffer();
            out.write(cbuf, off, len);
            return;
        }

        int b = off, t = off + len;
        while (b < t) {
            int d = min(nChars - nextChar, t - b);
            System.arraycopy(cbuf, b, cb, nextChar, d);
            b += d;
            nextChar += d;
            if (nextChar >= nChars)
                flushBuffer();
        }
    }
}