ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프록시 패턴 (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

Designed by Tistory.