프록시 패턴(Proxy Pattern)은 다른 객체에 대한 접근을 제어하기 위해 대리자 역할을 하는 객체를 사용하는 디자인 패턴입니다. 이 패턴은 객체의 접근을 통제하거나, 실제 객체를 대신해 간접적인 인터페이스를 제공할 때 유용합니다. 프록시는 클라이언트의 요청을 받아 실제 객체에 전달하거나, 요청을 가로채어 추가적인 작업을 수행할 수 있습니다.
프록시 패턴의 구성 요소
- Subject (주체): 실제 객체와 프록시 객체가 공유하는 인터페이스입니다. 여기에는 클라이언트가 호출할 메서드들이 정의됩니다.
- Real Subject (실제 객체): 실제 작업을 수행하는 객체입니다.
- Proxy (프록시 객체): 실제 객체에 대한 접근을 제어하는 역할을 합니다.
프록시 패턴의 종류와 예시
1. 가상 프록시 (Virtual Proxy)
- 목적: 실제 객체의 생성이 비용이 많이 드는 경우, 객체를 실제로 필요할 때까지 지연하여 생성합니다.
- 예시: 큰 이미지 파일을 로드할 때 가상 프록시를 사용하여, 실제로 이미지가 필요할 때만 로드합니다.
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 from disk: " + filename);
    }
    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;
    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 image = new ProxyImage("large_image.jpg");
        image.display(); // 이미지가 처음 호출될 때 로드됨
        image.display(); // 이미 로드된 이미지를 다시 표시
    }
}
2. 보호 프록시 (Protection Proxy)
- 목적: 객체에 대한 접근을 제어하여, 사용자의 권한에 따라 특정 메서드의 접근을 제한합니다.
- 예시: 접근 제어를 위해 보호 프록시를 사용하여, 사용자의 권한에 따라 문서의 내용을 볼 수 있도록 합니다.
interface Document {
    void display();
}
class RealDocument implements Document {
    private String content;
    public RealDocument(String content) {
        this.content = content;
    }
    @Override
    public void display() {
        System.out.println("Displaying document: " + content);
    }
}
class ProtectionProxyDocument implements Document {
    private RealDocument realDocument;
    private String userRole;
    public ProtectionProxyDocument(String content, String userRole) {
        this.realDocument = new RealDocument(content);
        this.userRole = userRole;
    }
    @Override
    public void display() {
        if ("Admin".equals(userRole)) {
            realDocument.display();
        } else {
            System.out.println("Access Denied: You do not have permission to view this document.");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Document document = new ProtectionProxyDocument("Confidential Report", "Guest");
        document.display(); // 접근 거부 메시지 출력
        Document adminDocument = new ProtectionProxyDocument("Confidential Report", "Admin");
        adminDocument.display(); // 문서 내용 출력
    }
}
3. 원격 프록시 (Remote Proxy)
- 목적: 실제 객체가 원격 서버에 있을 때, 이를 대신해 로컬에서 작업을 수행합니다.
- 예시: 원격 서버의 계산 작업을 프록시를 통해 수행합니다.
interface Calculator {
    int add(int a, int b);
}
class RemoteCalculator implements Calculator {
    @Override
    public int add(int a, int b) {
        System.out.println("Performing remote addition");
        return a + b;
    }
}
class CalculatorProxy implements Calculator {
    private RemoteCalculator remoteCalculator;
    @Override
    public int add(int a, int b) {
        if (remoteCalculator == null) {
            remoteCalculator = new RemoteCalculator();
        }
        return remoteCalculator.add(a, b);
    }
}
public class Main {
    public static void main(String[] args) {
        Calculator calculator = new CalculatorProxy();
        System.out.println("Result: " + calculator.add(10, 20)); // 원격 계산 수행
    }
}
4. 캐싱 프록시 (Caching Proxy)
- 목적: 반복적인 요청에 대해 동일한 결과를 반환할 때, 결과를 캐싱하여 성능을 향상시킵니다.
- 예시: 자주 조회되는 데이터를 캐싱하여 데이터베이스에 대한 반복적인 쿼리를 줄입니다.
interface DataFetcher {
    String fetchData();
}
class RealDataFetcher implements DataFetcher {
    @Override
    public String fetchData() {
        System.out.println("Fetching data from the database...");
        return "Data from database";
    }
}
class CachingProxyDataFetcher implements DataFetcher {
    private RealDataFetcher realDataFetcher;
    private String cachedData;
    @Override
    public String fetchData() {
        if (cachedData == null) {
            realDataFetcher = new RealDataFetcher();
            cachedData = realDataFetcher.fetchData();
        } else {
            System.out.println("Returning cached data");
        }
        return cachedData;
    }
}
public class Main {
    public static void main(String[] args) {
        DataFetcher dataFetcher = new CachingProxyDataFetcher();
        System.out.println(dataFetcher.fetchData()); // 실제 데이터베이스에서 데이터 가져옴
        System.out.println(dataFetcher.fetchData()); // 캐시된 데이터 반환
    }
}
5. 스마트 프록시 (Smart Proxy)
- 목적: 객체에 대한 접근을 제어하면서, 객체에 추가적인 기능을 제공합니다.
- 예시: 객체의 참조 횟수를 관리하는 스마트 프록시를 구현합니다.
interface Resource {
    void use();
}
class RealResource implements Resource {
    @Override
    public void use() {
        System.out.println("Using resource");
    }
}
class SmartProxyResource implements Resource {
    private RealResource realResource;
    private int referenceCount = 0;
    @Override
    public void use() {
        if (realResource == null) {
            realResource = new RealResource();
        }
        referenceCount++;
        System.out.println("Resource is being used. Reference count: " + referenceCount);
        realResource.use();
    }
    public void release() {
        if (referenceCount > 0) {
            referenceCount--;
            System.out.println("Resource released. Reference count: " + referenceCount);
            if (referenceCount == 0) {
                realResource = null;
                System.out.println("Resource is no longer needed and has been deallocated.");
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
        SmartProxyResource resourceProxy = new SmartProxyResource();
        resourceProxy.use();   // 참조 횟수 증가
        resourceProxy.use();   // 참조 횟수 증가
        resourceProxy.release(); // 참조 횟수 감소
        resourceProxy.release(); // 참조 횟수 0이 되어 리소스 해제
    }
}
프록시 패턴의 장점
- 객체 생성 지연: 가상 프록시를 사용하면, 실제 객체의 생성이 지연되어 메모리 사용량과 성능을 최적화할 수 있습니다.
- 예시: 큰 이미지 파일을 가상 프록시를 통해 필요할 때만 로드하여 메모리 사용을 줄임.
- 접근 제어: 보호 프록시를 통해 객체에 대한 접근을 제어하고, 보안 수준을 높일 수 있습니다.
- 예시: 보호 프록시를 사용하여, 사용자 권한에 따라 문서의 접근을 제한함.
- 추가 기능 제공: 스마트 프록시를 사용하면, 객체에 대한 추가 기능을 제공하거나, 객체의 상태를 관리할 수 있습니다.
- 예시: 스마트 프록시를 통해 객체의 참조 횟수를 관리하여 리소스를 효율적으로 사용함.
- 원격 객체 접근: 원격 프록시를 사용하여, 원격 서버에 있는 객체에 쉽게 접근할 수 있습니다.
- 예시: 원격 프록시를 통해 원격 서버에서 계산 작업을 수행함.
- 성능 향상: 캐싱 프록시를 사용하여 반복적인 요청에 대해 동일한 결과를 반환할 때, 결과를 캐싱하여 성능을 향상시킬 수 있습니다.
- 예시: 캐싱 프록시를 사용하여 데이터베이스에 대한 반복적인 쿼리를 줄임.
프록시 패턴을 사용하면 시스템의 유연성을 높이고 성능을 최적화할
프록시 패턴의 단점
- 복잡성 증가: 프록시 객체를 추가하면 시스템의 복잡성이 증가할 수 있으며, 유지보수가 어려워질 수 있습니다.
- 성능 오버헤드: 프록시를 통해 모든 요청이 처리되므로, 프록시 객체의 성능이 중요한 경우에는 성능 오버헤드가 발생할 수 있습니다.
- 코드 중복: 프록시 객체와 실제 객체 간의 인터페이스가 동일해야 하므로, 일부 코드 중복이 발생할 수 있습니다.
Share article