프록시 패턴(Proxy Pattern)
프록시(Proxy)는 대리자, 대변인이라는 뜻을 가진 단어다. 대리자/대변인은 다른 누군가를 대신해 그 역할을 수행하는 존재를 말한다. 따라서 프록시 패턴은 특정 객체의 대리자나 대변인 역할을 하는 프록시 객체를 제공하는 디자인 패턴입니다.
프록시 패턴을 사용함으로써 클라이언트는 특정 객체를 직접 참조하여 접근하는 것이 아닌 프록시 객체를 통해 상호작용합니다.
프록시 객체의 장단점
장점
접근 제어 : 클라이언트가 실제 객체에 직접 접근하지 않도록 제어하여 객체의 접근을 관리하고 권한 검사 등을 수행할 수 있습니다.
지연 초기화 : 실제 객체의 생성 및 초기화를 지연시키는 데 사용하여 필요한 순간만에 생성 및 초기화하여 성능을 최적화할 수 있습니다.
캐싱 : 결과를 캐싱하여 중복 계산을 피하고 성능을 향상시킬 수 있습니다.
유효성 검사 : 실제 객체에 접근하기 전에 데이터의 유효성 검사를 통해 검증할 수 있습니다.
원격 액세스 : 원격 프록시를 사용하여 다른 시스템에서 실행 중인 객체에 접근할 수 있으며 분산 시스템에서 객체 간 통신을 용이하게 한습니다.
단점
복잡성 증가 : 추가적인 객체를 도입하기 때문에 코드의 복잡성이 증가할 수 있습니다.
성능 저하 : 프록시 객체에 접근하는 데 추가적인 오버헤드가 발생할 수 있으며, 일부 성능 저하가 발생할 수 있습니다.
디자인 복잡성 : 프록시 패턴을 오용하면 코드를 과도하게 복잡하게 만들 수 있습니다.
프록시 패턴의 구조
프록시 패턴은 클라이언트가 접근할 Subject와 이에 대한 구현체인 RealSubject, Proxy가 존재합니다.
Subject : Proxy와 RealSubject가 모두 구현하는 인터페이스로 클라이언트가 프록시와 실제 대상을 동일하게 다룰 수 있도록 정의합니다.
RealSubject : 클라이언트가 직접 상호작용하는 실제 객체 입니다.
Proxy : RealSubject를 감싸며 실제 작업을 수행하는 주체로 클라이언트와 RealSubject 사이에 위치한 중간 객체다. RealSubject의 같은 이름의 메서드를 호출하며, 클라이언트의 요청을 처리하기 전이나 후에 추가적인 작업을 수행할 수 있습니다
프록시 패턴 예제
여행 예약 시스템이 있다고 가정합시다.
Reservation.java
public class Reservation {
private int reservationId;
private String travelerName;
private String destination;
public Reservation(int reservationId, String travelerName, String destination) {
this.reservationId = reservationId;
this.travelerName = travelerName;
this.destination = destination;
}
public int getReservationId() {
return reservationId;
}
public String getTravelerName() {
return travelerName;
}
public String getDestination() {
return destination;
}
}
프록시를 사용하지 않은 경우
ReservationService.java
import java.util.ArrayList;
import java.util.List;
public class ReservationService {
private List<Reservation> reservations = new ArrayList<>();
public void addReservation(Reservation reservation) {
reservations.add(reservation);
}
public Reservation findReservation(int reservationId) {
for (Reservation reservation : reservations) {
if (reservation.getReservationId() == reservationId) {
return reservation;
}
}
return null;
}
}
Main.java
public class Main {
public static void main(String[] args) {
ReservationService reservationService = new ReservationService();
// 여행 예약 추가
Reservation reservation = new Reservation(1, "홍길동", "프랑스 파리");
reservationService.addReservation(reservation);
// 여행 예약 검색
Reservation findReservation = reservationService.findReservation(1);
if (findReservation != null) {
System.out.println("예약 번호: " + findReservation.getReservationId() +
", 여행자: " + findReservation.getTravelerName() +
", 목적지: " + findReservation.getDestination());
} else {
System.out.println("예약 정보를 찾을 수 없습니다.");
}
}
}
프록시를 사용하는 경우
ReservationServiceSubject.java
public interface ReservationServiceSubject {
void addReservation(Reservation reservation);
Reservation findReservation(int reservationId);
}
ReservationServiceRealSubject.java
import java.util.ArrayList;
import java.util.List;
public class ReservationServiceRealSubject implements ReservationServiceSubject {
private List<Reservation> reservations = new ArrayList<>();
public void addReservation(Reservation reservation) {
reservations.add(reservation);
}
public Reservation findReservation(int reservationId) {
for (Reservation reservation : reservations) {
if (reservation.getReservationId() == reservationId) {
return reservation;
}
}
return null;
}
}
ReservationServiceProxy.java
class ReservationServiceProxy implements ReservationServiceSubject {
private ReservationServiceRealSubject reservationService = new ReservationServiceRealSubject();
public void addReservation(Reservation reservation) {
// 여행 예약을 추가하기 전에 어떤 작업을 수행할 수 있음
reservationService.addReservation(reservation);
}
public Reservation findReservation(int reservationId) {
// 여행 예약을 조회하기 전에 어떤 작업을 수행할 수 있음
return reservationService.findReservation(reservationId);
}
}
ProxyMain.java
public class ProxyMain {
public static void main(String[] args) {
ReservationServiceProxy reservationService = new ReservationServiceProxy();
// 여행 예약 추가
Reservation reservation = new Reservation(1, "홍길동", "프랑스 파리");
reservationService.addReservation(reservation);
// 여행 예약 검색
Reservation findReservation = reservationService.findReservation(1);
if (findReservation != null) {
System.out.println("예약 번호: " + findReservation.getReservationId() +
", 여행자: " + findReservation.getTravelerName() +
", 목적지: " + findReservation.getDestination());
} else {
System.out.println("예약 정보를 찾을 수 없습니다.");
}
}
}
정리
프록시 패턴의 중요 포인트는 다음과 같다.
프록시 패턴은 제어 흐름을 조정하기 위한 목적으로 중간에 계층을 도입하여 여러 기능을 추가하거나 제어하면서 실제 서비스를 대신 수행하는 패턴입니다.
추가로 앞서 설명한 예제의 구조를 살펴보면, 개방 폐쇄 원칙(OCP)과 의존성 역전 원칙(DIP)를 준수하고 있다.
프록시 패턴은 OCP와 DIP를 준수하도록 설계된 패턴이기 때문입니다.