프록시 패턴(Proxy Pattern)

허성재's avatar
Aug 14, 2024
프록시 패턴(Proxy Pattern)
프록시 패턴(Proxy Pattern)은 다른 객체에 대한 접근을 제어하기 위해 대리자 역할을 하는 객체를 사용하는 디자인 패턴입니다. 이 패턴은 객체의 접근을 통제하거나, 실제 객체를 대신해 간접적인 인터페이스를 제공할 때 유용합니다. 프록시는 클라이언트의 요청을 받아 실제 객체에 전달하거나, 요청을 가로채어 추가적인 작업을 수행할 수 있습니다.

프록시 패턴의 구성 요소

  1. Subject (주체): 실제 객체와 프록시 객체가 공유하는 인터페이스입니다. 여기에는 클라이언트가 호출할 메서드들이 정의됩니다.
  1. Real Subject (실제 객체): 실제 작업을 수행하는 객체입니다.
  1. 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이 되어 리소스 해제 } }

프록시 패턴의 장점

  1. 객체 생성 지연: 가상 프록시를 사용하면, 실제 객체의 생성이 지연되어 메모리 사용량과 성능을 최적화할 수 있습니다.
      • 예시: 큰 이미지 파일을 가상 프록시를 통해 필요할 때만 로드하여 메모리 사용을 줄임.
  1. 접근 제어: 보호 프록시를 통해 객체에 대한 접근을 제어하고, 보안 수준을 높일 수 있습니다.
      • 예시: 보호 프록시를 사용하여, 사용자 권한에 따라 문서의 접근을 제한함.
  1. 추가 기능 제공: 스마트 프록시를 사용하면, 객체에 대한 추가 기능을 제공하거나, 객체의 상태를 관리할 수 있습니다.
      • 예시: 스마트 프록시를 통해 객체의 참조 횟수를 관리하여 리소스를 효율적으로 사용함.
  1. 원격 객체 접근: 원격 프록시를 사용하여, 원격 서버에 있는 객체에 쉽게 접근할 수 있습니다.
      • 예시: 원격 프록시를 통해 원격 서버에서 계산 작업을 수행함.
  1. 성능 향상: 캐싱 프록시를 사용하여 반복적인 요청에 대해 동일한 결과를 반환할 때, 결과를 캐싱하여 성능을 향상시킬 수 있습니다.
      • 예시: 캐싱 프록시를 사용하여 데이터베이스에 대한 반복적인 쿼리를 줄임.
프록시 패턴을 사용하면 시스템의 유연성을 높이고 성능을 최적화할

프록시 패턴의 단점

  1. 복잡성 증가: 프록시 객체를 추가하면 시스템의 복잡성이 증가할 수 있으며, 유지보수가 어려워질 수 있습니다.
  1. 성능 오버헤드: 프록시를 통해 모든 요청이 처리되므로, 프록시 객체의 성능이 중요한 경우에는 성능 오버헤드가 발생할 수 있습니다.
  1. 코드 중복: 프록시 객체와 실제 객체 간의 인터페이스가 동일해야 하므로, 일부 코드 중복이 발생할 수 있습니다.
 
 
Share article

heo-gom