프록시 패턴은 객체의 대리자 역할을 하는 객체를 만들어 원본 객체에 접근하는 방식을 제어하는 디자인 패턴입니다. 이 패턴은 원본 객체를 직접 다루기 전에 여러 기능을 추가하거나 제어 로직을 넣고 싶을 때 유용하게 사용됩니다.
프록시 패턴은 실제 객체(Real Subject)에 대한 접근을 제어하거나, 원본 객체를 감싸서 추가적인 기능을 제공하기 위해 사용됩니다. 여기서 중요한 점은 클라이언트(사용자)가 실제 객체 대신 프록시 객체를 사용하게 된다는 것입니다.
프록시 패턴은 다양한 목적에 따라 사용되며, 동기화 기능도 그중 하나입니다. 프록시 객체가 원본 객체의 접근을 제어하면서 스레드 동기화 기능을 추가할 수 있습니다.
프록시 패턴은 다음과 같은 구성 요소를 가집니다:
멀티스레드 환경에서 동기화가 필요한 경우, 원본 객체를 변경하지 않고 프록시 객체에 동기화 기능을 추가할 수 있습니다. 이렇게 하면 코드의 복잡성을 줄이면서도 동기화 문제를 해결할 수 있습니다.
예시
프록시 패턴을 사용해 특정 메서드에 스레드 동기화 기능을 추가하는 예시를 보겠습니다:
// 주제 인터페이스
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() 메서드를 실행합니다.
프록시 패턴을 활용해 동기화 기능을 추가하는 것은 특히 멀티스레드 환경에서 공유 자원을 안전하게 관리할 때 유용합니다. 예를 들어, 원본 객체가 여러 스레드에 의해 동시에 접근되는 상황에서는 원본 객체에 동기화 기능을 추가하지 않고, 프록시 객체에 동기화 로직을 넣어서 문제를 해결할 수 있습니다.
프록시 패턴을 사용할 때 성능 최적화가 필요한 주요 부분은 프록시를 통해 추가된 기능들이 원본 객체에 대한 호출 성능을 저하시키는지 여부입니다.
프록시 패턴은 동기화 외에도 다양한 기능을 추가하는 데 활용될 수 있습니다. 다음은 프록시 패턴을 통해 추가할 수 있는 주요 기능들입니다:
로깅(Logging): 메서드가 호출될 때마다 로그를 남기거나, 특정 이벤트가 발생할 때 로그를 기록하는 기능을 프록시에서 처리할 수 있습니다.
보안 검증(Security Check): 프록시에서 접근 권한을 제어하여, 사용자가 특정 메서드에 접근할 수 있는지를 확인할 수 있습니다.
캐싱(Caching): 자주 호출되는 메서드의 결과를 캐싱해두고, 필요할 때마다 캐시된 값을 반환하여 성능을 향상시킬 수 있습니다. 특히 데이터베이스 조회나 네트워크 요청과 같은 비용이 큰 연산에서 유용합니다.
지연 초기화(Lazy Initialization): 실제 객체의 생성이 무겁거나 필요하지 않을 때, 프록시가 이를 대신 처리하고, 진짜 객체가 필요해지는 시점에서 객체를 생성하는 방식으로 최적화를 할 수 있습니다.
트랜잭션 관리(Transaction Management): 메서드 호출이 트랜잭션의 시작과 끝을 처리해야 하는 경우, 트랜잭션 관리를 프록시에서 수행하여 코드의 복잡성을 줄일 수 있습니다.
이처럼 프록시 패턴을 통해 객체에 다양한 기능을 추가하여 유연하고 효율적인 설계를 할 수 있습니다.
클라이언트가 실시간으로 원본 객체의 메서드 호출 여부를 제어하려면, 프록시 객체에서 특정 조건을 추가하여 메서드 호출을 차단하거나 진행하도록 만들 수 있습니다. 예를 들어, 클라이언트가 특정 시간대나 특정 상태에서만 원본 객체의 메서드에 접근할 수 있게 하려면, 프록시에서 조건을 검사한 후 메서드를 호출할지 말지를 결정할 수 있습니다.
다음은 그 예시입니다:
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() 메서드를 통해 클라이언트가 실시간으로 원본 객체에 대한 접근을 제어할 수 있습니다. 특정 조건이 만족될 때만 원본 객체의 메서드가 실행되며, 그렇지 않을 경우 호출이 차단됩니다.