프록시 패턴 (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
참고
'디자인 패턴' 카테고리의 다른 글
커맨드 패턴 (Command Pattern) (0) | 2022.08.02 |
---|---|
책임 연쇄 패턴 (Chain of Responsibility Pattern) (0) | 2022.08.02 |
플라이웨이트 패턴 (Flyweight Pattern) (0) | 2022.07.22 |
퍼사드 패턴 (Facade Pattern) (0) | 2022.07.19 |
데코레이터 패턴(Decorator Pattern) (0) | 2022.07.19 |