구조패턴 : 클래스나 객체를 상속과 합성을 통하여 더 큰 구조를 만드는 패턴
프록시 패턴
프록시는 대리자,대변인을 의미한다.
대리자는 누군가를 대신하여 업무를 수행하고 대신 진행하는 업무에 대해 결과가 변하지 않아야한다.
원본 객체를 대리하여 대신 처리함으로써 로직의 "흐름을 제어"하는 패턴
실생활에선 비서가 있을 수 있다.
비서의 장점?
1.상사는 비서가 자신의 기본 업무를 대신 수행함으로써 자신의 업무에 집중할 수 있다.
2. 또한 비서는 기존 업무의 효율성을 위하여 데이터 선처리,검증 등 추가 업무를 기존 업무에 영향 없이 진행할 수 있다.
비서의 단점?
1. 기존 인력이 전보다 1명 늘었다.
2. 상사의 업무를 여러 명의 비서가 나눠서 진행할 경우 외부인 입장에서 어떤 비서가 어떤 업무를 하는지 파악이 어렵다.
위를 프로그래밍 관점에서 보라보면 아래와 같이 표현할 수 있다.
프록시 패턴의 장점?
1. 원본객체는 프록시객체가 자신의 기본 업무를 대신 수행함으로써 기존 코드는 해야할 일만 한다.
2. 또한 프록시객체는 기존 서비스로직에 대한 효율성을 위하여 데이터 선처리, 검증 등 추가적인 기능을 기존 코드에 영향 없이 추가할 수 있다.
3. 원본객체의 생성을 필요시점까지 미룰 수 있다.
프록시 패턴의 단점?
1. 원본 객체 외 프록시 객체까지 생성해야 하므로 빈번한 객체 생성 필요한 경우 성능 저하
2. 코드에 대한 복잡도 증가
구조
- Subject
프록시객체와 원본 객체의 최상위 계층(인터페이스)
클라이언트 입장에선 Proxy역할과 원본객체를 구분하지 않고 사용할 수 있다.
- RealSubject
원본 객체
- Proxy
프록시 객체로써 멤버변수로 원본 대상 클래스 변수가 존재한다.
해당 원본대상 객체 통하여 실제 동작을 수행한다.
프록시는 흐름제어만 할 뿐 결과값에 대한 조작,변경해선 안된다.
프록시 종류
기본 프록시
/* 클라이언트 입장에선 프록시객체든 원본객체든 알 필요없이
* 동일한 메소드로 서비스 로직을 수행하기 위해
* 인터페이스 선언(프록시, 원본 클래스가 구현)
*/
public interface Subject {
void operation();
}
/* 원본 객체 */
public class RealSubject implements Subject{
@Override
public void operation() {
System.out.println("원본객체 operation 로직 수행");
}
}
/* 프록시 객체 */
public class Proxy implements Subject{
Subject realSubject; //실제 메소드를 해당 객체를 통하여 호출 위해 선언
public Proxy(Subject realSubject){
setRealSubject(realSubject);
}
void setRealSubject(Subject realSubject){
this.realSubject = realSubject;
}
@Override
public void operation() {
System.out.println("프록시 객체를 통한 원본객체 operation 수행 시작");
this.realSubject.operation(); //내부 존재하는 원본객체 참조를 통해 메소드 호출
}
}
public class ProxyMain {
public static void main(String[] args) {
Subject proxy = new Proxy(new RealSubject());
proxy.operation();
}
}
가상 프록시
필요 시점까지 원본 객체 생성을 연기한다.
가벼운 일은 프록시 객체가, 기존 업무는 원본 객체가 수행한다.
생성하기에 비용이 많이 드는 객체가 준비가 완료될때까지 대신하여 주는 프록시이다.
이미지 뷰어 프로그램을 만든다고 가정한다.
고해상도의 이미지를 경로에서 불러 메모리에 적재하고 showImg()메소드 호출 시 사용자에 보여지도록 하는 구조다.
참고로 메모리 적재 작업은 굉장히 무거운 작업이다.
프록시패턴 적용전)
public class HighResolutionImg implements ResolutionImg {
Img img;
String imgUrl;
public HighResolutionImg(String imgUrl){
this.imgUrl = imgUrl;
System.out.println(this.imgUrl + "의 img 메모리 적재 수행");
this.img = new Img(imgUrl); //메모리 적재
}
public void showImg(){
System.out.println(imgUrl + "의 img 렌더링");
}
}
public class ProxyMain {
public static void main(String[] args) {
HighResolutionImg img1 = new HighResolutionImg("www.url1.com");
HighResolutionImg img2 = new HighResolutionImg("www.url2.com");
img1.showImg();
}
}
/결과/
www.url1.com의 img 메모리 적재 시작
www.url2.com의 img 메모리 적재 시작
www.url1.com의 img 렌더링
이처럼 구현 시 img1,img2 메모리에 적재가 완료된 후 img1이 보여지게 되므로 늦게 사용자에게 보이게 된다.
프록시패턴 적용 후)
클라이언트가 showImg() 요청한 이미지만 메모리 적재하여 렌더링되도록 흐름을 제어하면 된다.
가상 프록시를 활용하여 지연 초기화로 객체의 생성을 늦출 수 있다.
public interface ResolutionImg {
public void showImg();
}
/* 프록시 클래스*/
public class ImgProxy implements ResolutionImg{
ResolutionImg resolutionImg;
String imgUrl;
public ImgProxy(String imgUrl){
this.imgUrl=imgUrl;
}
@Override
public void showImg() {
resolutionImg= new HighResolutionImg(this.imgUrl);
resolutionImg.showImg();
}
}
public class ProxyMain {
public static void main(String[] args) {
ResolutionImg img1 = new ImgProxy("www.url1.com");
ResolutionImg img2 = new ImgProxy("www.url2.com");
img1.showImg();
}
}
/결과/
www.url1.com의 img 메모리 적재 시작
www.url1.com의 img 렌더링
showImg()메소드를 호출해야 그제서야 메모리 적재가 진행되기에 불필요한 자원낭비가 발생하지 않는다.
다시 말하면, 객체 생성을 지연시켜 원하는 시점에 생성되도록 패턴을 적용한 것이다.
보호 프록시
프록시는 클라이언트가 작업을 수행할 수 있는 권한이 있는지 확인하고 검사 가능한 경우에만 요청을 대상으로 전달한다.
로깅 프록시
프록시는 서비스 메서드를 실행하기 전달하기 전에 로깅을 하는 기능을 추가하여 재정의한다.
원격 프록시
클라이언트 입장에선 프록시를 통해 객체를 이용하는 것이니 원격이든 로컬이든 신경 쓸 필요가 없으며, 프록시는 진짜 객체와 통신을 대리하게 된다.
캐싱 프록시
데이터가 큰 경우 캐싱하여 재사용을 유도
해당 블로그에 설명이 잘 되어 있다. 구조 패턴 - 프록시(Proxy) (tistory.com)
Dynamic Proxy
☕ 누구나 쉽게 배우는 Dynamic Proxy 다루기 (tistory.com)
참고
💠 프록시(Proxy) 패턴 - 완벽 마스터하기 (tistory.com)