디자인 패턴

프록시 패턴 (Proxy Pattern)

개발정리 2022. 7. 22. 22:51

프록시 패턴 (Proxy Pattern)


  • 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴
  • 특정 객체에 접근하기 전에 프록시 객체를 먼저 지난 후 접근하게 된다. 
    • 초기화 지연, 접근 제어, 로깅, 캐싱 등 다양하게 응용하여 사용할 수 있다.

 

 

 

 

프록시 패턴 적용 전


Client 가 startGame 이 실행되고 종료되기까지 얼마나 시간이 걸리는지 알기 위해서는 Client 코드의 main 시작 부분과 startGame 마지막 부분에 시간을 재면 된다. 

public class Client {
  public static void main(String[] args) {
    GameService gameService = new GameService();
    gameService.startGame();
  }
}
public class GameService {
  public void startGame() {
    System.out.println("게임을 시작합니다.");
  }
}

 

 

 

프록시 패턴 적용 후


방법 1 

GameService 를 전혀 손대지 않고 측정하는 방법을 살펴보자.

(여기에선 시간을 고의적으로 지연시키기 위해 sleep 을 사용한 것이다.)

public class Client {
  public static void main(String[] args) throws InterruptedException {
    GameService gameService = new GameServiceProxy(); //프록시 사용
    gameService.startGame();
  }
}
public class GameService {
  public void startGame() throws InterruptedException {
    System.out.println("게임을 시작합니다.");
    Thread.sleep(1000L);
  }
}
public class GameServiceProxy extends GameService {
  @Override
  public void startGame() throws InterruptedException {
    long before = System.currentTimeMillis();
    super.startGame();
    System.out.println(System.currentTimeMillis() - before);
  }
}

 

Service 클래스를 상속받는 ServiceProxy(프록시 클래스) 를 만들어 startGame 메서드를 오버라이드 하였고, 이 곳에 시간 측정 코드를 추가하였다.

 

이 방법은 기존코드를 전혀 변경하지 않고 프록시 패턴을 적용하는 방법이였다.

 

 

방법2 

이번에는 아래 프록시 패턴 클래스 다이어그램을 적용해보자.

Client 를 보면 프록시 안에 사용할 Service 객체를 넣어주고 있다.

public class Client {
  public static void main(String[] args) {
    // Client 가 DefaultGameService 를 쓰기 위해서는 프록시를 거쳐야함.
    GameService gameService = new GameServiceProxy(new DefaultGameService());
    gameService.startGame();
  }
}

 

DefaultGameService 는 코드를 더 유연하게 하기 위해 GameService 인터페이스를 정의하고 구현하였다.

// Subject interface
public interface GameService {
  void startGame();
}
// Real Subject interface
public class DefaultGameService implements GameService {
  @Override
  public void startGame() {
    System.out.println("게임을 시작합니다!");
  }
}

 

프록시는 이전 데코레이터 패턴에서 본 것 처럼 GameService 를 가지고 있고, 이를 구현하고 있다.

public class GameServiceProxy implements GameService {

    private GameService gameService;

    public GameServiceProxy(GameService gameService) {
        this.gameService = gameService;
    }

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        gameService.startGame();
        System.out.println(System.currentTimeMillis() - before);
    }
}

 

프록시의 startGame 메서드를 보면 수행시간을 측정하는 로직과 Client 한테 받은 GameService 타입 객체의 startGame 메서드를 실행시키고 있다. 이렇게 하면 시간 측정외에도 다양한 로직을 추가할 수 있고, GameService 도 유연하게 변경할 수 있다.

 

 

또한  프록시 객체 내에서 GameService 를 결정하게 하여 지연 초기화(Lazy Initiallization)를 적용 할 수 있다.

아래 코드를 살펴보자.

public class GameServiceProxy implements GameService {

    private GameService gameService;

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        // Lazy Initiallization
        // 지연 로딩 외에도 특정 로직을 삽입하여 gameService 를 원하는 객체로 넣을 수 있다.
        if(this.gameService == null) this.gameService = new DefaultGameService();
        gameService.startGame();
        System.out.println(System.currentTimeMillis() - before);
    }
}
public class Client {
  public static void main(String[] args) {
    // Client 가 DefaultGameService 를 쓰기 위해서는 프록시를 거쳐야함.
    GameService gameService = new GameServiceProxy();
    gameService.startGame();
  }
}

 

 

 

장점과 단점


장점

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

 

단점

  • 코드의 복잡도가 증가한다.

 

 

 

실무에선 어떻게 쓰이나?


자바

  • 다이나믹 프록시
  • java.lang.reflect.Proxy

 

스프링

  • 스프링 AOP

 

 

 

참고


 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com