0. 이 글을 쓰게 된 이유
Spring을 공부하던 중 프록시 패턴을 만나게 되었다. 원래 알고 있던 패턴이긴 하지만 이 참에 정리하고 가는게 좋을 것 같아서 이 글을 쓰게 되었다.
1. 프록시 패턴이란?
토비의 스프링에서는 이 프록시를 두가지로 분류한다.
- 클라이언트가 타깃에 접근하는 방법을 제어하기 위한 프록시(GoF의 디자인 패턴의 프록시 패턴과 같음.)
- 타깃에 부가적인 기능을 부여하기 위한 프록시(데코레이터 패턴이라고 한다.
여기서는 위의 1번에 해당하는 전형적인 프록시 패턴에 대해서 얘기할 것이다. 데코레이터 패턴도 거의 비슷한 구조이긴하다.
프록시 구조를 객체 다이어그램으로 나타내면 위와 같다.
간단하게 얘기하면 클라이언트가 실제 객체인 RealSubject에 접근하려면 Proxy 객체를 통해서 접근할 수 있는 구조이다.
Proxy클래스는 RealSubject 클래스와 같은 인터페이스를 구현한 구조로 되어있고, 클라이언트는 Proxy 클래스를 호출하여 사용한다.
Proxy클래스의 메소드는 Proxy 클래스 단에서 처리하거나 RealSubject로 위임하여 처리한다.
1.1 프록시 종류
프록시 패턴은 구조가 간단하여 활용하기 쉽다. 그래서 다양한 프록시 종류가 있고 여기서는 디자인 패턴에서 소개하는 3가지만 알아보자.
- 가상 프록시(virtual proxy)
- 생성 비용이 많이 드는 RealObject 대신 상대적으로 생성 비용이 적은 Proxy 객체를 만들어두고, 실제로 RealObject가 필요할 때에 객체를 생성한다.
- 실제 객체의 생성 시점을 제어할 수 있다. 클라이언트가 처음 요청 및 접근할 때만 실제 객체각 생성 되며 이휴는 프록시를 참조하여 실제 객체를 대신할 수 있다.
- RealObject의 생성 비용이 높을 때 활용하기 좋다.
- 보호 프록시(protection proxy)
- RealObject에 대한 실제 접근을 제어한다.
- 이는 객체별로 접근 제어 권한이 다를 때 유용하게 사용할 수 있다.
- 대표적으로 Collections.unmodifiableCollection를 예로 들 수 있다.
- 원격지 프록시(remote proxy)
- 서로 다른 주소 공간 혹은 다른 메모리에 존재하는 객체를 가리키는 객체이다. 원격지 프록시는 로컬에 위치하고, 가르키고 있는 실제 객체는 다른 주소 혹은 다른 메모리에 존재한다고 보면 된다. (리모컨 생각하면 된다.)
- 자바에서는 대표적으로 RMI(원격 메소드 호출)이 이에 해당한다.
내 생각에는 프록시 패턴은 예시를 통해서 학습하는 것이 제일 쉬운 방법이니 예시로 넘어가자
2. 예시
프록시의 대표적인 예제로 가상 프록시와 보호 프록시 예제를 살펴보자.
2.1 가상 프록시 예제
2.1.1 Subject Interface
public interface Subject {
void method();
}
2.1.2 Real Object
public class RealObject implements Subject {
@Override
public void method() {
System.out.println("real method");
}
}
2.1.3 Proxy
public class Proxy implements Subject {
private Subject realObject;
@Override
public void method() {
realObject = new RealObject();
realObject.method();
}
}
클라이언트는 프록시를 통해서 실제 객체에 접근할 수 있다. 이 때 실제 객체는 생성 비용이 높다고 가정한다.
프록시는 실제 객체가 쓰이는 시점인 method()가 호출되는 시점에만 실제 객체를 생성한다.
2.1.4 Client
public static void main(String[] args) {
Subject proxy = new Proxy(); // 실제 객체 생성 X
proxy.method(); // 실제 객체 생성 O
}
2.2 보호 프록시
2.2.1 Subject Interface(Pizza)
public interface Pizza {
int size();
ArrayList<String> getToppings();
void addTopping(String topping);
}
Pizza라는 인터페이스가 있고 위와 같이 세가지 메소드가 있다.
2.2.2 Real Object(PizzaImpl)
public class PizzaImpl implements Pizza {
private ArrayList<String> toppings = new ArrayList<>();
@Override
public int size() {
return toppings.size();
}
@Override
public ArrayList<String> getToppings() {
return toppings;
}
@Override
public void addTopping(String topping) {
toppings.add(topping);
}
}
실제 객체가 되는 클래스이다.
2.2.3 Proxy(PizzaProxy)
public class PizzaProxy implements Pizza {
private Pizza realPizza;
private boolean isAdmin;
public PizzaProxy(Pizza realPizza, boolean isAdmin) {
this.realPizza = realPizza;
this.isAdmin = isAdmin;
}
@Override
public int size() {
return realPizza.size();
}
@Override
public ArrayList<String> getToppings() {
return realPizza.getToppings();
}
@Override
public void addTopping(String topping) {
if (isAdmin) {
realPizza.addTopping(topping);
} else {
throw new UnsupportedOperationException();
}
}
}
프록시 객체이다. 이 객체는 실제 객체와 마찬가지로 Pizza 인터페이스를 구현했다.
실제 객체를 가지고 있는 realPizza 멤버 변수와 권한을 나타내는 isAdmin 멤버 변수가 있다. 이들은 이 객체를 생성할 때 초기화 된다.
size()와 getTopping() 메소드는 권한에 상관없이 실제 객체에다가 위임하여 처리한다.
addTopping() 메소드는 isAdmin이 true일때만 실제 객체에다가 위임하며, 아닐 경우 예외를 던진다. 이런 식으로 권한 설정이 가능한 것이다.
3. 프록시 패턴의 장점
- 생성 비용이 무거운 객체의 생성 시점을 늦출 수 있어서 메모리 낭비를 방지
- 권한을 설정하기에 적절하다.
- 데이터 공유를 해야하는 경우 효율적이다.
4. 프록시 패턴의 단점
- 인터페이스를 구현하고 위잉ㅁ하는 코드를 작성하기가 번거롭다.
일부 메소드는 그냥 위임만하는 메소드일 수도 있지만 이를 모두 구현해야한다.
또는 인터페이스의 메소드가 추가되거나 변경될 때마다 함께 수정해줘야한다는 부담이 있다. - 코드 중복이 되기가 쉽다.
프록시의 특성상 하나의 특징을 가지고 있으니 메소드마다 코드가 중복될 가능성이 높다.
위의 단점들은 InvocationHandler 인터페이스를 구현한 리플렉션 방식으로 대부분 해결이 가능하다!
'디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 템플릿/콜백(Template/Callback) 패턴 (0) | 2022.09.30 |
---|---|
[디자인 패턴] 전략(Strategy) 패턴 (2) | 2022.09.29 |
[디자인 패턴] 팩토리 메소드(Factory Method) 패턴 (0) | 2022.09.28 |
[디자인 패턴] 디자인 패턴(Design Pattern)이란? (0) | 2022.09.28 |
[디자인 패턴] 싱글톤(Singleton) 패턴 (0) | 2022.09.28 |