프록시 패턴이란
프록시는 대리인이라는 뜻으로, 무언가를 대신 처리한다는 의미이다.
어떤 객체를 사용하고자 할 때, 객체를 직접 참조하는 것이 아니라 해당 객체를 대행하는 프록시 객체를 통해
대상 객체에 접근하는 방식을 사용하면 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 반드시 필요한 시점까지 객체의 생성을 미룰 수 있다.
프록시 패턴 사용 시기
- 접근을 제어하거가 기능을 추가하고 싶은데, 기존의 특정 객체를 수정할 수 없는 상황일때
- 초기화 지연, 접근 제어, 로깅, 캐싱 등, 기존 객체 동작에 수정 없이 가미하고 싶을 때
프록시 패턴 사용시 이점
- 개방 폐쇄 원칙(OCP) 준수 : 기존 대상 객체의 코드를 변경하지 않고 새로운 기능을 추가할 수 있다.
- 단일 책임 원칙(SRP) 준수 : 대상 객체는 자신의 기능에만 집중 하고, 그 이외 부가 기능을 제공하는 역할을 프록시 객체에 위임하여 다중 책임을 회피 할 수 있다.
프록시 패턴 사용시 단점
많은 프록시 클래스를 도입해야 하므로 코드의 복잡도가 증가한다.
예를들어 여러 클래스에 로깅 기능을 가미 시키고 싶다면, 동일한 코드를 적용함에도 각각의 클래스에 해당되는 프록시 클래스를 만들어서 적용해야 되기 때문에 코드량이 많아지고 중복이 발생 된다.
자바에서는 리플렉션에서 제공하는 동적 프록시(Dynamic Proxy) 기법을 이용해서 해결할 수 있다. (후술)
기본적인 프록시 패턴 적용 방법
서비스 클래스
package proxyPattern;
public class Service implements IService {
public String runSomething() {
return "서비스 짱!!!";
}
}
서비스 클래스의 프록시 클래스
package proxyPattern;
public class Proxy implements IService {
IService service1;
public String runSomething() {
System.out.println("호출에 대한 흐름 제어가 주목적, 반환 결과를 그대로 전달");
service1 = new Service();
return service1.runSomething();
}
}
프록시 객체를 활용하여 서비스의 함수 호출
package proxyPattern;
public class ClientWithProxy {
public static void main(String[] args) {
// 프록시를 이용한 호출
IService proxy = new Proxy();
System.out.println(proxy.runSomething());
}
}
예제로 알아보기
상황 가정
사람의 일종인 서병렬(sbl) 이 밥을 먹는 action을 수행하는 상황이다.
서병렬은 종교가 무교였지만 기독교를 다니게 된 상태이며 따라서
밥을 먹는 action을 수행하기 전에 기도라는 추가적인 action을 수행해야 한다.
프록시 패턴이 활용되지 않은 코드
먼저 무교 시절의 서병렬을 살펴보자
밥 먹기 action 추상화를 위한 IPerson 인터페이스
package proxy;
public interface IPerson {
void haveAMill();
}
밥 먹기 action이 구현된 서병렬 클래스
package proxy;
public class SBL implements IPerson{
@Override
public void haveAMill() {
System.out.println("밥 먹기");
}
}
식사!
package proxy;
public class HaveAMill {
public static void main(String[] args) {
IPerson sbl = new SBL();
sbl.haveAMill();
}
}
여기서 평생 무교일 것 같던 서병렬이 기독교를 믿기 시작했다! 따라서 식사 로직을 수정해야한다.
여기서 만약 SBL 클래스를 직접 수정하여 식사 전에 기도하게한다면,
서병렬이라는 클래스의 식사 액션이 기독교에 종속되게 된다.
나중에 무교가 되었을 때 수정해줘야 하는 상황이 발생하므로 불편하고, 개방 폐쇄 원칙을 준수하지 않았다고 할 수 있다.
또한 HaveAMill이라는 함수는 “식사” 라는 로직이 구현되어있을 것이라고 기대되지, 그 안에 기도 로직이 들어있는 것은 단일 책임 원칙에 위배된다.
이러한 이유로 프록시 패턴을 사용하는 것이 좋은데, 어떤 식으로 사용되는 지 확인해보자
프록시 패턴을 활용한 코드
서병렬을 직접 멤버로 포함하는 Proxy 클래스를 작성한다.
package proxy;
public class PrayProxy implements IPerson{
private SBL sbl;
public PrayProxy(SBL sbl){
this.sbl = sbl;
}
@Override
public void haveAMill() {
System.out.println("기도하기");
sbl.haveAMill();
}
}
이제 서병렬의 종교가 현재 무교인지 기독교인지 불교인지 상관하지 않고, 프록시 객체를 불러와서 식사를 시키면 된다!
package proxy;
public class HaveAMill {
public static void main(String[] args) {
IPerson sbl = new PrayProxy(new SBL());
sbl.haveAMill();
}
}
실제 서병렬 클래스는 건들지 않았으므로
다시 무교가 된다면 그냥 SBL 객체를 불러와 식사를 시키면 된다.