프록시 패턴(Proxy Pattern)

서버란·2024년 9월 19일

CS 지식

목록 보기
10/25

프록시 패턴은 객체의 대리자 역할을 하는 객체를 만들어 원본 객체에 접근하는 방식을 제어하는 디자인 패턴입니다. 이 패턴은 원본 객체를 직접 다루기 전에 여러 기능을 추가하거나 제어 로직을 넣고 싶을 때 유용하게 사용됩니다.

1. 프록시 패턴(Proxy Pattern) 개념

프록시 패턴은 실제 객체(Real Subject)에 대한 접근을 제어하거나, 원본 객체를 감싸서 추가적인 기능을 제공하기 위해 사용됩니다. 여기서 중요한 점은 클라이언트(사용자)가 실제 객체 대신 프록시 객체를 사용하게 된다는 것입니다.

프록시 패턴은 다양한 목적에 따라 사용되며, 동기화 기능도 그중 하나입니다. 프록시 객체가 원본 객체의 접근을 제어하면서 스레드 동기화 기능을 추가할 수 있습니다.

2. 프록시 패턴의 구조

프록시 패턴은 다음과 같은 구성 요소를 가집니다:

  1. Subject(주제 인터페이스): 실제 객체와 프록시가 구현해야 할 공통 인터페이스입니다.
  2. RealSubject(실제 객체): 원본 객체로, 프록시가 대신해서 접근을 제어하거나 기능을 추가할 대상입니다.
  3. Proxy(프록시 객체): 실제 객체에 대한 접근을 제어하는 대리 객체입니다. 원본 객체에 추가적인 로직(동기화, 접근 제어, 로깅 등)을 포함시킬 수 있습니다.

3. 프록시 패턴을 활용한 동기화 기능 추가

멀티스레드 환경에서 동기화가 필요한 경우, 원본 객체를 변경하지 않고 프록시 객체에 동기화 기능을 추가할 수 있습니다. 이렇게 하면 코드의 복잡성을 줄이면서도 동기화 문제를 해결할 수 있습니다.

예시
프록시 패턴을 사용해 특정 메서드에 스레드 동기화 기능을 추가하는 예시를 보겠습니다:

// 주제 인터페이스
public interface Service {
    void performAction();
}

// 실제 객체
public class RealService implements Service {
    @Override
    public void performAction() {
        System.out.println("Real Service: 작업 수행 중...");
    }
}

// 프록시 객체: 동기화 기능 추가
public class SynchronizedServiceProxy implements Service {
    private final Service realService;

    public SynchronizedServiceProxy(Service realService) {
        this.realService = realService;
    }

    @Override
    public synchronized void performAction() {
        System.out.println("프록시: 동기화 시작...");
        realService.performAction();
        System.out.println("프록시: 동기화 끝...");
    }
}

// 사용 예시
public class ProxyPatternExample {
    public static void main(String[] args) {
        Service realService = new RealService();
        Service synchronizedService = new SynchronizedServiceProxy(realService);

        // 프록시를 통해 실제 객체에 접근
        synchronizedService.performAction();
    }
}

동작 설명
1. RealService: 실제로 동작을 수행하는 원본 객체입니다. performAction() 메서드가 실제로 작업을 처리합니다.
2. SynchronizedServiceProxy: RealService 객체에 동기화 기능을 추가한 프록시입니다. performAction() 메서드를 호출할 때 동기화(synchronized) 키워드를 사용해 여러 스레드가 동시에 이 메서드를 호출하지 않도록 보장합니다.
3. ProxyPatternExample: 클라이언트는 실제 객체를 직접 사용하지 않고, 프록시 객체를 사용해 동기화된 방식으로 performAction() 메서드를 실행합니다.

4. 프록시 패턴의 이점

  • 기능 확장: 원본 객체를 수정하지 않고, 프록시를 통해 기능을 확장할 수 있습니다. 예를 들어, 동기화, 캐싱, 접근 제어, 로깅 등의 기능을 프록시에서 처리할 수 있습니다.
  • 지연 초기화: 원본 객체의 생성이 무겁다면, 프록시가 필요할 때까지 객체 생성을 지연시킬 수 있습니다.
  • 보안 제어: 접근 권한을 제한하거나, 특정 조건에서만 원본 객체에 접근하도록 제어할 수 있습니다.

5. 프록시 패턴을 통해 동기화 추가하는 시나리오

프록시 패턴을 활용해 동기화 기능을 추가하는 것은 특히 멀티스레드 환경에서 공유 자원을 안전하게 관리할 때 유용합니다. 예를 들어, 원본 객체가 여러 스레드에 의해 동시에 접근되는 상황에서는 원본 객체에 동기화 기능을 추가하지 않고, 프록시 객체에 동기화 로직을 넣어서 문제를 해결할 수 있습니다.

6. 프록시 패턴과 동기화의 장점

  • 분리된 관심사: 동기화 로직을 원본 객체에서 분리함으로써 코드의 유지보수성과 가독성을 높일 수 있습니다.
  • 유연성: 필요에 따라 동기화 기능을 포함하는 프록시를 사용할 수 있으며, 원본 객체는 다른 용도로 재사용이 가능합니다.
  • 성능 최적화: 동기화가 꼭 필요한 메서드만 프록시를 통해 관리하여 성능을 개선할 수 있습니다.

정리

  • 프록시 패턴은 원본 객체에 접근하기 전에 프록시 객체를 통해 추가적인 기능(동기화, 로깅, 접근 제어 등)을 제공하는 디자인 패턴입니다.
  • 동기화 기능을 추가하려면 원본 객체를 수정하지 않고, 프록시 객체에서 동기화 로직을 추가할 수 있습니다.
  • 이는 멀티스레드 환경에서 공유 자원 접근 시 동기화 문제를 해결하는 좋은 방법입니다.

Q1: 프록시 패턴을 사용할 때 성능 최적화가 필요한 부분은 어떤 것인가요?

프록시 패턴을 사용할 때 성능 최적화가 필요한 주요 부분은 프록시를 통해 추가된 기능들이 원본 객체에 대한 호출 성능을 저하시키는지 여부입니다.

  • 예를 들어, 동기화(synchronized)를 추가하면 멀티스레드 환경에서 잠금 대기 시간이 길어질 수 있으므로, 자주 호출되는 메서드에 불필요하게 동기화 로직을 넣지 않도록 주의해야 합니다.
  • 또한, 로깅이나 보안 검증과 같은 추가 기능도 너무 많은 연산을 요구할 경우, 원래 간단한 메서드 호출이 복잡해지면서 성능에 영향을 줄 수 있습니다.
  • 성능을 최적화하려면, 프록시로 추가된 기능이 꼭 필요한 곳에서만 사용되도록 하고, 가능하면 성능에 큰 영향을 주지 않도록 최소한의 부하만 주는 방식으로 설계하는 것이 중요합니다.

Q2: 동기화 외에 프록시 패턴을 활용하여 추가할 수 있는 다른 기능은 어떤 것이 있을까요?

프록시 패턴은 동기화 외에도 다양한 기능을 추가하는 데 활용될 수 있습니다. 다음은 프록시 패턴을 통해 추가할 수 있는 주요 기능들입니다:

  1. 로깅(Logging): 메서드가 호출될 때마다 로그를 남기거나, 특정 이벤트가 발생할 때 로그를 기록하는 기능을 프록시에서 처리할 수 있습니다.

  2. 보안 검증(Security Check): 프록시에서 접근 권한을 제어하여, 사용자가 특정 메서드에 접근할 수 있는지를 확인할 수 있습니다.

  3. 캐싱(Caching): 자주 호출되는 메서드의 결과를 캐싱해두고, 필요할 때마다 캐시된 값을 반환하여 성능을 향상시킬 수 있습니다. 특히 데이터베이스 조회나 네트워크 요청과 같은 비용이 큰 연산에서 유용합니다.

  4. 지연 초기화(Lazy Initialization): 실제 객체의 생성이 무겁거나 필요하지 않을 때, 프록시가 이를 대신 처리하고, 진짜 객체가 필요해지는 시점에서 객체를 생성하는 방식으로 최적화를 할 수 있습니다.

  5. 트랜잭션 관리(Transaction Management): 메서드 호출이 트랜잭션의 시작과 끝을 처리해야 하는 경우, 트랜잭션 관리를 프록시에서 수행하여 코드의 복잡성을 줄일 수 있습니다.

이처럼 프록시 패턴을 통해 객체에 다양한 기능을 추가하여 유연하고 효율적인 설계를 할 수 있습니다.

Q3: 프록시 패턴을 활용해 클라이언트가 실시간으로 원본 객체의 메서드 호출 여부를 제어하는 방법은 무엇인가요?

클라이언트가 실시간으로 원본 객체의 메서드 호출 여부를 제어하려면, 프록시 객체에서 특정 조건을 추가하여 메서드 호출을 차단하거나 진행하도록 만들 수 있습니다. 예를 들어, 클라이언트가 특정 시간대나 특정 상태에서만 원본 객체의 메서드에 접근할 수 있게 하려면, 프록시에서 조건을 검사한 후 메서드를 호출할지 말지를 결정할 수 있습니다.

다음은 그 예시입니다:

public class AccessControlProxy implements Service {
    private final Service realService;
    private boolean accessAllowed;

    public AccessControlProxy(Service realService) {
        this.realService = realService;
        this.accessAllowed = true;  // 기본값은 접근 허용
    }

    // 접근 제어 메서드
    public void setAccessAllowed(boolean accessAllowed) {
        this.accessAllowed = accessAllowed;
    }

    @Override
    public void performAction() {
        if (accessAllowed) {
            System.out.println("프록시: 접근이 허용됨. 작업 수행 중...");
            realService.performAction();
        } else {
            System.out.println("프록시: 접근이 거부됨. 작업을 수행하지 않습니다.");
        }
    }
}

이렇게 하면, setAccessAllowed() 메서드를 통해 클라이언트가 실시간으로 원본 객체에 대한 접근을 제어할 수 있습니다. 특정 조건이 만족될 때만 원본 객체의 메서드가 실행되며, 그렇지 않을 경우 호출이 차단됩니다.

profile
백엔드에서 서버엔지니어가 된 사람

0개의 댓글