• 본 포스팅은 2021년 12월 16일에 수정되었습니다.

프록시는 실제로 액션을 취하는 객체를 대신해서 대리자 역할을 해준다. 프록시 패턴을 사용하게 되면 프록시 단계에서 권한을 부여할 수 있는 이점이 생기고 필요에 따라 객체를 생성시키거나 사용하기 때문에 메모리를 절약할 수 있는 이점도 생긴다. 프록시 패턴이 하는 일은 한마디로 자신이 보호하고 있는 객체에 대한 액세스 권한을 제어하는 것이다.

example

간단한 예제를 통해 좀 더 알아보도록 하자. 이 예제에서는 프록시를 사용함으로써 실제 인스턴스 사용 과정을 관여하고 메모리를 절약하는 방법을 예제로 나타내고 있다.

main class

실질적으로 사용되는 핵심 코드는 아래의 2가지이다. 하나는 인터페이스고 하나는 인터페이스를 상속받은 클래스이다. 이 상태에서 일반적으로 코딩을 하게 되면 무조건 CommandExcutorImpl를 호출하여 객체를 생성시키고 높은 cost의 runCommand 메소드 작업으로 인해 메모리 낭비가 예상된다.

public interface CommandExecutor {
    public void runCommand(String cmd) throws Exception ;
}
public class CommandExcutorImpl implements CommandExecutor {
    @Override
    public void runCommand(String cmd) throws IOException {
        // 뭔가 높은 cost의 작업이 필요함.
        Runtime.getRuntime().exec(cmd);
        System.out.println("cmd: " + cmd);
    }
}

proxy class

위의 단점을 보완하기 위에 프록시 클래스를 작성하였다. 일단 똑같은 인터페이스를 상속받음으로 인터페이스의 일관성을 유지한다. 그리고 생성자에서 CommandExcutorImpl 클래스를 인스턴화 시키고, 인스턴스화 된 executor 객체의 runCommand 메소드를 프록시 클래스의 runCommand 메소드에서 액세스를 결정한다.

public class CommandExecutorProxy implements CommandExcutor {
    private boolean isAdmin;
    private CommandExecutor executor;

    public CommandExecutorProxy(String user, String pwd) {
        if ( "seotory".equals(user) && "pw".equels(pwd) ) {
            isAdmin = true;
        }
        executor = new CommandExcutorImpl();
    }

    @Override
    public void runCommand(String cmd) throws Exception {
        if (isAdmin) {
            executor.runCommand(cmd);
        } else {
            if (cmd.trim().startsWith("rm")) {
                throw new Exception("rm is only admin");
            } else {
                executor.runCommand(cmd);
            }
        }
    }
}

testing

public class ProxyPatternTest {
    public static void main(String[] args) {
        CommandExcutor executor = new CommandExecutorProxy("seotory", "is_not_pw");
        try {
            executor.runCommand("ls -al");
            executor.runCommand("rm -rf *"); // proxy 클래스에 의해 에러가 날 것이다.
        } catch (e) {
            System.out.println("Exception Message: " + e.getMessage());
        }
    }
}

결과 output

cmd: ls -al
cmd: rm -rf *
Exception Message: rm is only admin

즉 위와 같이 프록시를 작성하면 인터페이스를 일관성있게 유지시키면서 액세스 권한을 부여할 수 있고 더불어 메모리 절약을 할 수 있게 된다.

적용 포인트

  • 가상 프록시
    높은 cost 객체 대신 스켈레톤 객체(인터페이스만 존재하고 실제로 인스턴스를 생성하지 않는 객체)를 사용하여 실질적으로 객체가 필요할때까지 높은 cost의 객체 생성을 지연시켜 메모리를 절약할 수 있다.
  • 원격 프록시
    서로 다른 머신에 있는 객체에 대해 제공할 수 있다(또는 객체를 사용할 수 있다). 일반적인 예는 JAVA의 RMI이다.
    • http://www.informit.com/articles/article.aspx?p=1398608&seqNum=3
  • 보호 프록시
    객체에 대해 액세스 할 수있는 권한을 부여할 수 있다.
  • 정교한 참조
    프록시 객체에 정교한 작업을 부여할 수 있다. 예를들어 객체를 생성할 때 카운팅 기능을 추가적으로 작업할 수 있다.

패턴 비교

  • 어뎁터 패턴
    어뎁터 패턴은 실제 오브젝트와 다른 인터페이스를 제공하여 실제 오브젝트를 사용할 수 있도록 도와준다. 그러나 프록시 패턴은 실제 오브젝트와 동일한 인터페이스를 제공한다.
  • 데코레이터 패턴
    데코레이터 패턴은 런타임에 실제 객체에 동작을 추가한다. 그러나 프록시는 동작을 제어하지 않고 동작을 변경하지 않는다.
Tags : java

java composite pattern (컴포지트 패턴)

컴포지트 패턴이란 클래스의 구조적 디자인 패턴으로 단일 객체와 복합 객체를 동일하게 컨트롤 할 수 있게끔 도와주는 패턴이다. 컴포지트 패턴은 아래와 같이 3가지의 요소에 의해 이루어진다. base component base component는 composition(구성자)을 위한 인터페이스로 구성된다. 클라이언트 클래스에서는 base component의 인터페이스를 사용하여 작업하게...

더보기

java adapter pattern (어뎁터 패턴)

adapter pattern은 관계가 없는 인터페이스들이 같이 일할 수 있도록 도와주는 디자인 패턴이다. 이 두 인터페이스를 이어주는 인터페이스를 adapter라 부른다. 아래 예제를 보면서 이해해보도록하자. volt class public class Volt { private int volts; public Volt(int v) { this.volts = v; }...

더보기

java builder pattern (빌더 패턴)

bulider pattern은 창조적 디자인 패턴이며 이것은 factory pattern 또는 abstract factory pattern과 매우 비슷하다. 이 패턴에 들어가기 전에 factory pattern과 abstract factory pattern들의 문제점(수 많은 attributes을 사용해야 패턴을 사용할 수 있는 점)에 대해 먼저 알아보자. factory pattern과 abstract factory pattern에는...

더보기

java abstract factory pattern (추상 팩토리 패턴)

abstract factory pattern은 생산적인 디자인 패턴 중 하나로써 좀 더 factory를 좀 더 생산적으로 만들어 낼 수 있다는 점외에는 factory pattern과 매우 비슷하다. 여러분이 java의 factory design pattern에 익숙하다면, 보통은 어떠한 인풋에 대해 factory class안에서 if-else로 다른 sub-class를 반환하는 일련의...

더보기