반응형

의도

  • 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴.
    • 초기화 지연, 접근 제어, 로깅, 캐싱 등 다양하게 응용해 사용할 수 있다.
  • 다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리채움자 역할을 하는 객체를 둔다.

 

장점

  • 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있다.
  • 기존 코드가 해야 하는 일만 유지할 수 있다.
  • 기능 추가 및 초기화 지연 등으로 다양하게 활용할 수 있다.

 

단점

  • 코드의 복잡도가 증가한다.
  • 여러 다른 클래스에 동일한 기능(시간 측정)을 적용 시킨다면, 동일한 코드를 적용함에도 각각의 클래스에 해당하는 프록시 객체를 만들어서 적용해야 하기 때문에 코드의 중복이 많이 발생한다.
    • 자바에서는 리플렉션에서 제공하는 다이나믹 프록시를 이용해서 해결할 수 있다.

 

활용성

  • 원격지 프록시(remote proxy)
    • 서로 다른 주소 공간에 존재하는 객체를 가리키는 대표 객체로 로컬 환경에 위치한다.
  • 가상 프록시(virtual proxy)
    • 요청이 있을 때만 필요한 고비용 객체를 생성한다.
  • 보호용 프록시(protection proxy)
    • 원래 객체에 대한 실제 접근을 제어한다.
    • 객체별로 접근 제어 권한이 다를 때 유용하게 사용할 수 있다.
  • 스마트 참조자(smart reference)
    • 원시 포인터의 대체용 객체
    • 실제 객체에 접근이 일어날 때 추가적인 행동을 수행한다.

 

결과

  • 프록시 패턴은 어떤 객체에 접근할 때 추가적인 간접화 통로를 제공한다.
  • 프록시 종류별 간접화 통로의 쓰임새
    • 원격지 프록시는 객체가 다른 주소 공간에 존재한다는 사실을 숨길 수 있다.
    • 가상 프록시는 요구에 따라 객체를 생성하는 등 처리를 최적화할 수 있다.
    • 보호용 프록시 및 스마트 참조자는 객체가 접근할 때마다 추가 관리를 책임진다. 객체를 생성할 것인지 삭제할 것인지를 관리한다.
  • 기록 시점 복사(copy-on-write)
    • 요구가 들어올 때만 객체를 생성하는 개념과 관련있다.
    • 복잡하고 큰 객체를 생성하는 것은 비용이 크다. 따라서 사본이 변경되지 않고 원본과 똑같다면 굳이 새롭게 생성하지 않아도 된다. 사본이 원본과 달라진 경우 원본을 이용해 새로운 사본을 만들어 내야 한다.
    • 프록시를 사용해서 복사 절차를 미룸으로써 사본이 수정될 때만 실제 복사 비용을 물도록 할 수 있다.
    • 기록 시점 복사는 중량급 객체에 대한 복사 비용을 현격하게 줄여준다.

 

협력 방법

  • 프록시 클래스는 자신이 받은 요청을 RealSubject 객체에 전달한다.

 

구조

  • 프로그램 실행 중 프록시 구조 객체 다이어그램

 

실제 구현 구조

 

소스코드

public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameServiceImplProxy();
        gameService.startGame();
    }
}
//Subject
public interface GameService {
    void startGame();
}
//RealSubject
public class GameServiceImpl implements GameService {
    @Override
    public void startGame() {
        System.out.println("게임 시작");
    }
}
//Proxy
public class GameServiceImplProxy implements GameService {

    private GameService gameService;

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        //lazy init
        if (gameService == null) {
            this.gameService = new GameServiceImpl();
        }
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
        //게임 로딩 과정
        try {
            for(int i = 0; i <= 100; i++) {
                bufferedWriter.write(String.valueOf(i));
                if(i % 10 == 0) {
                    bufferedWriter.write(System.lineSeparator());
                }
            }
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //게임 로딩 후 게임 시작
        gameService.startGame(); // realSubject -> Request();
        System.out.println(System.currentTimeMillis() - before);

    }
}

 

관련 패턴

  • 적응자는 자신이 개조할 객체가 정의된 인터페이스와 다른 인터페이스를 제공한다. 이에 반해, 프록시는 자신이 상대하는 대상과 동일한 인터페이스를 제공한다.
  • 장식자는 프록시와 구현 방법이 비슷한데, 장식자는 그 사용 목적이 하나 이상의 서비스를 추가하기 위해서이고, 프록시는 객체에 대한 접근을 제어하는 목적이라는 점에서 차이가 있다.
    • 구현 방법에서도 차이가 있다. 예를 들어, 보호성 프록시는 장식자 구현 방법과 거의 유사하나, 원격 프록시는 실제 처리 대상을 직접 참조하도록 관리하지 않고 간접적 접근 방법을 관리한다. 이 간접적 접근 방법은 호스트 식별자, 호스트 머신 내 주소 등을 포함한다. 가상 프록시는 파일 이름과 같은 간접적 참조자를 정의하지만, 궁극적으로는 직접적 참조자를 얻어온 후 이를 사용한다.

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

http://www.cs.unc.edu/~stotts/GOF/hires/pat4efso.htm

코딩으로 학습하는 GoF의 디자인 패턴, 백기선

 

반응형

+ Recent posts