Proxy 패턴은 단순하면서도 자주 쓰이는 패턴이고 활용 방식도 다양하다. 이 글에서는 Proxy 패턴과 함께 그 다양한 활용 방법에 대해서 이야기 해 보도록 하겠다.


Proxy 패턴의 클래스 다이어그램


Proxy 패턴의 구현(기본형)

interface Subject {

    public Object action();

}

class RealSubject implements Subject {

    public RealSubject(){}

    public Object action() { /* do something */ return null;}

}

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){

        realSubject = new RealSubject();

    }

    public Object action() {

        return realSubject.action();

    }

}


Proxy 패턴은 Proxy 패턴의 기본형을 어떤 방식으로 변형하느냐에 따라 달라진다.

Proxy 패턴의 변형에는 두가지 방향이 있다.


1. RealSubject의 생성 시점

2. Proxy 객체의 action()에서 하는 일


위의 두가지 변형을 조합함으로써 활용도를 다르게 가지고 갈 수 있다.


동적 생성 프록시

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        if(realSubject == null){

            realSubject = new RealSubject();

        }

        return realSubject.action();

    }

}

동적 생성 프록시는 프록시 객체가 실제 객체를 생성하지 않고 시작한다. 그리고 실제 요청(action() 메소드 호출)이 들어 왔을 때 실제 객체를 생성한다. 이 구현은 실제 객체의 생성에 많은 자원이 소모 되지만 사용 빈도는 낮을 때 쓰는 방식이다.


자원관리 프록시

class RealSubject implements Subject {

    private String fileName;

    @Override

    public void action() {

        System.out.println("Displaying " + fileName);

    }

    public void loadFromDisk(String fileName){

        this.fileName = fileName;

        System.out.println("Loading " + fileName);

    }

}

class Proxy implements Subject{

    private RealSubject realSubject;

    private String fileName = "";

    public Proxy(){

        realSubject = new RealSubject();

    }

   

    @Override

    public void action() {

        realSubject.action();

    }

   

    public void loadFromDisk(String fileName){

        if(this.fileName.equals(fileName)) return;

        this.fileName = fileName;

        realSubject.loadFromDisk(fileName);

    }

}

자원관리 프록시는 실제 객체가 비용이 많이 드는 자원에 대한 생성을 담당할 때 쓰인다. 실제 객체는 기본적인 자원 생성(위 예제에서는 loadFromDisk() 메소드)를 담당하고, 프록시는 이 자원이 이미 생성되었는지를 체크한다. 만약 이미 생성된 자원이라면 자원에 대한 생성을 스킵한다. 이를 통해서 불필요하게 자원을 중복으로 생성하는 것을 방지한다.


가상 프록시

class Proxy implements Subject{

//    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        return virtualAction();

    }

   

    private Object virtualAction(){ /* do something */ return null; }

}

실제 객체가 여러가지 이유로 존재하지 않을 경우에 사용되는 방식이다. 특히 여러 단위의 조직이 협업을 할 때 인터페이스는 정의되어 있으나 실제 구현은 아직 되어 있지 않은 경우에 이 가상 프록시를 사용한다. 가상 프록시에 시뮬레이션 된 동작을 하도록 구현해 두고 개발을 진행한 후, 실제 객체가 완성된 이후에 프록시를 실제 객체로 대체 시킨다. 이런 방식을 통해서 실제 객체를 붙여 보기 전에 발생할 수 있는 문제점들을 미리 테스트 해 볼 수 있기 때문에 통합 작업에서 발생하는 문제점들을 줄일 수 있다.


원격 프록시

class Proxy implements Subject{

//    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        return remoteAction();

    }

   

    private Object remoteAction(){

        connect();

        Object result = getData();

        disconnect();

        return result;

    }

   

    private void connect(){/* connect to remote */}

    private Object getData(){/* data request and wait response */ return null;}

    private void disconnect(){/* disconnect from remote */}

}

프록시가 마치 원격에 있는 실제 객체처럼 동작하도록 하는 방법이다. 프록시 객체를 사용하는 객체들은 실제 객체와 동일한 인터페이스를 통해 프록시를 사용하고, 데이터 통신이나 변환과 같은 작업은 프록시 객체 내부에서 수행하도록 한다.


AOP(Aspect Oriented Programming) 프록시

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){

        realSubject = new RealSubject();

    }

    public Object action() {

        preProcess();

        Object result = realSubject.action();

        postProcess();

        return result;

    }

    private void preProcess(){/* 선행 작업 */}

    private void postProcess(){/* 후행 작업 */}

}

AOP는 모든 객체에 공통으로 적용되는 기능들을 구현하는 개발 방법을 말한다. 예를 들어 어떤 객체를 멀티 쓰레딩 환경에서 보호해야 한다거나, 어떤 메소드 호출이 걸리는 시간을 측정하거나, 어떤 동작에 대한 트랜젝션을 작성하는 등의 경우이다. 이런 것들은 실제 객체의 행위와 별개로 이루어질 수 있다.

위의 코드에서 보면 action() 메소드가 preProcess() 메소드를 먼저 호출 한 후 realSubject의 action()을 호출한다. 그리고 이후에 postProcess() 메소드를 호출하도록 되어 있다. 이 preProcess()와 postProcess()에 어떤 작업을 구현해 넣느냐에 따라서 관점(Aspect)이 달라진다. 만약 mutex.lock()과 mutex.unlock()을 넣는다면 멀티쓰레드 안정성을 제공할 수 있다. 만약 preProcess()와 postProcess()의 호출 시각을 저장하고 둘의 차를 계산한다면 action() 메소드 실행에 걸린 시간을 측정할 수 있다.

이렇게 실제 객체는 그대로 두고 모든 객체들이 공통으로 수행해야 할 일들을 프록시 객체를 통해서 구현할 수 있다.(실제 Spring과 같은 프레임워크에서 AOP를 구현하는 방식은 reflection을 이용하는 방식이다. 단지 여기서는 같은 형태의 구현을 프록시로 할 수 있다는 점 만 보여주는 것이다.)

'5.디자인패턴' 카테고리의 다른 글

Iterator 패턴  (0) 2016.09.17
Enum Abstract Factory 패턴  (0) 2016.09.16
Observer 패턴  (0) 2016.09.16
Abstract Factory 패턴  (0) 2016.09.16
Factory Method 패턴  (4) 2016.09.16
Posted by 이세영2
,