프록시(Proxy) 패턴
프록시(Proxy) 패턴은 실제 객체에 대한 접근을 제어하기 위해 대리 객체(Proxy)를 사용하는 구조적 디자인 패턴입니다. 프록시는 실제 객체의 대리인 역할을 하며, 객체의 생성이나 사용을 지연하거나, 접근 제어, 로깅, 캐싱 등의 추가 작업을 수행할 수 있습니다.
이 패턴은 원본 객체에 직접 접근하지 않아야 하는 상황에서 사용됩니다.
문제점: 객체의 직접 사용이 비효율적이거나 불가능한 경우
어떤 객체가 비용이 많이 드는 리소스를 포함하거나, 네트워크를 통해 원격으로 호출되는 경우 객체를 직접 생성하고 호출하는 것이 비효율적일 수 있습니다. 예를 들어, 이미지 로더에서 큰 이미지를 즉시 로드하면 시간이 오래 걸리기 때문에 이미지 로드를 지연하거나 로딩 과정을 제어해야 할 수 있습니다.
class RealImage {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk(); // 객체 생성 시 리소스 소모
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
public void display() {
System.out.println("Displaying: " + filename);
}
}
public class Main {
public static void main(String[] args) {
// 객체 생성 시 이미지가 즉시 로드됨 (비효율적)
RealImage image1 = new RealImage("photo1.jpg");
RealImage image2 = new RealImage("photo2.jpg");
image1.display();
image2.display();
}
}
문제점
- 비용이 높은 초기화: 이미지를 사용할 때마다 매번 로드하므로 비용이 많이 듭니다.
- 지연 로딩 불가능: 이미지를 즉시 로드하기 때문에 불필요한 리소스를 낭비합니다.
해결 방법: Proxy 패턴 적용
프록시 패턴을 사용해 지연 로딩(lazy loading)을 구현할 수 있습니다. 프록시 객체가 원본 객체의 대리인 역할을 하며, 실제 객체가 필요할 때만 생성됩니다.
공통 인터페이스 정의
interface Image {
void display();
}
실제 객체 구현
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying: " + filename);
}
}
프록시 클래스 구현
class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) { // 지연 로딩 구현
realImage = new RealImage(filename);
}
realImage.display();
}
}
클라이언트 코드
public class Main {
public static void main(String[] args) {
// 프록시 객체 생성
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");
// 이미지가 처음 요청될 때만 로딩됨
image1.display(); // Loading image: photo1.jpg
// Displaying: photo1.jpg
image1.display(); // Displaying: photo1.jpg (이미 로드된 이미지 사용)
image2.display(); // Loading image: photo2.jpg
// Displaying: photo2.jpg
}
}
Proxy 패턴의 동작 원리
- 프록시 객체는 실제 객체에 대한 참조를 보유합니다.
- 클라이언트는 프록시 객체를 통해 원본 객체에 접근하며, 프록시는 필요할 때 실제 객체를 생성하거나 추가 작업을 수행합니다.
- 지연 로딩이나 접근 제어가 필요할 때 사용됩니다.
Proxy 패턴의 장점과 단점
장점
- 지연 로딩 구현: 실제 객체가 필요할 때만 생성되므로 리소스를 절약할 수 있습니다.
- 접근 제어: 프록시 객체가 원본 객체에 대한 접근을 제어할 수 있습니다.
- 추가 기능 구현: 원본 객체에 로깅, 캐싱, 인증 등의 기능을 쉽게 추가할 수 있습니다.
- 결합도 감소: 클라이언트와 실제 객체 간의 결합도를 낮출 수 있습니다.
단점
- 복잡성 증가: 프록시 객체를 구현해야 하므로 코드가 복잡해질 수 있습니다.
- 성능 오버헤드: 프록시를 통해 호출할 때 추가적인 메서드 호출로 인한 성능 저하가 발생할 수 있습니다.
- 모든 상황에 적합하지 않음: 간단한 객체에 프록시를 적용하면 불필요한 복잡성을 초래할 수 있습니다.
결론
프록시 패턴은 지연 로딩, 접근 제어, 로깅, 캐싱 등 다양한 용도로 사용되는 강력한 패턴입니다. 클라이언트가 실제 객체의 구현을 알 필요 없이 프록시를 통해 제어할 수 있기 때문에, 유연하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.
하지만, 프록시 패턴을 사용하면 코드의 복잡성이 증가할 수 있으므로, 복잡한 시스템에서 효율적인 사용이 필요합니다. 이 패턴은 특히 대규모 시스템에서 리소스를 최적화하거나 원격 객체와의 통신을 제어할 때 매우 유용합니다.