[디자인 패턴] Proxy 패턴

한낱·2023년 11월 5일

디자인 패턴

목록 보기
6/6

프록시 패턴이란?

프록시는 '대리인'이라는 뜻으로, 현실에서 일을할 사람을 대신할 사람으로 대리인을 사용하는 것처럼 객체지향에서 본인 객체를 대신하여 '대리인 객체'가 대신하여 일을 처리하는 것을 말한다.

이름 붙인 프린터 예제

  • PrinterProxy 클래스 : 이름을 붙이거나, 변경하거나 표시하는 작업을 수행
  • Printer 클래스 : 실제 print하는 단계에서 호출됨
    - Printer 클래스와 PrinterProxy 클래스를 나누어서 작업해야 하는 이유는 Printer 클래스에는 무거운 작업을 가지고 있기 때문
  • Printable 인터페이스 : PrinterProxy 클래스와 Printer 클래스의 공통 인터페이스

Printer 클래스

: 본인과 대리인 중 본인을 나타내는 클래스

public class Printer implements Printable {
	private String name;
    
    public Printer() {
    	// 생성자 안에 무거운 작업이 존재
    	heavyJob("Printer 인스턴스 생성 중");
    }
    
    public Printer(String name) {
    	this.name = name;
        heavyJob("Printer 인스턴스(" + name + ") 생성 중");
    }
    
    // 이름 설정 메서드
    @Override
    public void setPrinterName(String name) {
    	this.name = name;
    }
    
    // 이름을 얻는 메서드
    @Override
    public String getPrinterName() {
    	return name;
    }
    
    // 출력 메서드
    @Override
    public void print(String string) {
    	System.out.println("=== " + name + " ===");
        System.out.println(string);
    }
    
    private void heavyJob(String msg) {
    	System.out.print(msg);
        for (int i = 0; i < 5; i++) {
        	try {
            	Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.print(".");
        }
        System.out.println("완료");
    }
}

Printable 인터페이스

public interface Printable {
	// 이름 설정 메서드
	public abstract void setPrinterName(String name);
    // 이름을 얻는 메서드
    public abstract String getPrinterName();
    // 출력 메서드
    public abstract void print(String string);
}

PrinterProxy 클래스

: 본인과 대리인 중 대리인을 담당하는 클래스

public class PrinterProxy implements Printable {
	// 이름
	private String name;
   	// `본인` 저장
    private Printer real;
    
    public PrinterProxy() {
    	this.name = "No Name";
        // 아직 본인이 만들어지지 않은 상태
        this.real = null;
    }
    
    public PrinterProxy(String name) {
    	this.name = name;
        // 아직 본인이 만들어지지 않은 상태
        this.real = null;
    }
    
    // 이름 설정 메서드
    @Override
    public synchronized void setPrinterName(String name) {
    	if (real != null) {
        	// 본인이 만들어진 상황이라면 본인에게도 name 설정
        	real.setPrinterName(name);
        }
        this.name = name;
    }
    
    // 이름 가져오는 메서드
    @Override
    public String getPrinterName() {
    	return name;
    }
    
    @Override
    public void print(String string) {
    	// print는 proxy(대리인)이 할 수 없는,
        // 본인이 해야하는 범위의 동작이므로
        // realize 메서드를 호출하여 '본인'을 생성한다.
    	realize();
        // realize를 하고 나면 real에 '본인'이 저장됨.
        // real.print 호출을 통해 '위임'한다.
        real.print(string);
    }
    
    @Override
    private synchronized void realize() {
    	if (real == null) {
        	real = new Printer(name);
        }
    }
}
  • Printer 인스턴스는 정말 본인이 필요할 때에 생성된다.
    - setPrinterName이나 getPrinterName을 통해서는 생성되지 않고,
    • print를 호출할 때에나 생성된다.
  • Printer 클래스는 PrinterProxy의 존재를 모른다.
    - 본인이 PrinterProxy를 경유해서 호출되는지 직접 호출되는지 모름
  • PrinterProxy는 Printer를 안다.
    - 필드로 가지고 있음.

Synchronized

: 동기화가 필요한 메서드나 코드블럭 앞에 적어 동기화한다.
위 코드에서 synchronized가 걸려있는 함수는 setPrinterName과 realize로 모두 real을 변경할 수 있는 함수이다.
-> 자바는 멀티스레드 환경이므로 다른 스레드에서 개별적으로 real을 변경하려 하면서 PrinterProxy와 Printer의 name 값이 달라질 수 있는데 synchronized를 통해 이를 방지한다.

Main 클래스

public class Main {
	public class void main(String[] args) {
    	// Main 클래스는 PrinterProxy를 경유하여 Printer를 이용한다.
    	Printable p = new PrinterProxy("Alice");
        System.out.println("이름은 현재 " + p.getPrinterName() + "입니다.");
        p.setPrinterName("Bob");
        System.out.println("이름은 현재 " + p.getPrinterName() + "입니다.);
        // 본인만이 할 수 있는 일이 호출되었을 때 Printer 인스턴스가 만들어진다.
        p.print("Hello, world.");
    }
}
  • 투과적이다 : Main 클래스는 Printable 인터페이스를 사용하고 있기 때문에 실제로 호출하는 곳이 PrinterProxy이지 Printer인지 상관하지 않는다. (PrinterProxy를 경유하여 Printer를 사용하거나 Printer를 자체로 사용하거나 상관 없기 때문에 투과적이다라는 표현을 사용한다.)

Proxy 패턴 다이어그램

  • Subject(본인) : 예시에서 Printable 인터페이스에 해당
    Subject 덕분에 Client는 Proxy와 RealSubject의 차이를 알 필요가 없어진다.
  • Proxy(대리인) : 예시에서 PrinterProxy 클래스에 해당
    RealSubject가 정말로 필요한 순간에 RealSubject를 생성한다.
  • RealSubject(실제 본인) : 예시에서 Printer 클래스에 해당
    Proxy만으로 동작할 수 없는 상황에 생성된다.
  • Client : 예시에서 Main 클래스에 해당

Proxy 패턴을 사용하는 이유

  1. 대리인이 필요한 이유 [무거운 처리]
    : Subject를 생성하는 데에 무거운 처리가 필요하므로 Subject의 생성 시점을 최대한 미루어 사용자의 스트레스를 줄인다.
    ex. 문서 안에 그래픽 객체가 삽입되어 있을 때 그래픽 객체를 생성하는 것은 시간이 오래 걸리는 작업이므로 문서를 열 때 모든 그래픽 객체를 생성하는 대신, 각각의 그래픽 객체가 화면에 표시될 때 그래픽 객체를 생성한다.

  2. 대리인과 본인을 나눈 이유 [분할하여 통치하기]
    : 둘을 분리하지 않고 지연 평가 기능을 넣을 수도 있으나 분리함으로써 개별적인 수정이 가능해진다.
    - PrinterProxy 클래스의 구현을 바꾸어도 Printer 클래스를 수정할 필요가 없다.
    - 지연평가를 할지 말지 결정을 PrinterProxy를 사용할지 말지를 통해 단순하게 구현이 가능하다.

HTTP 프록시

: HTTP 서버와 HTTP 클라이언트 사이에서 웹 페이지 캐싱 등을 하는 소프트웨어

  • 웹 캐싱을 할 때, 웹 서버에 접속하여 페이지를 일일이 가져오는 것이 아니라 HTTP 프록시가 캐싱해놓은 페이지를 대신 가져온다.
  • 최신 정보가 필요할 때나 웹 페이지의 유효 기간이 지났을 때 웹 서버를 통해 웹 페이지를 가져온다.

-> Clinet: 웹 브라우저, Proxy : HTTP 프록시, RealSubject : 웹 서버

profile
제일 재밌는 개발 블로그(희망 사항)

0개의 댓글