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

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

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("완료");
}
}
public interface Printable {
// 이름 설정 메서드
public abstract void setPrinterName(String name);
// 이름을 얻는 메서드
public abstract String getPrinterName();
// 출력 메서드
public abstract void print(String string);
}
: 본인과 대리인 중 대리인을 담당하는 클래스
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);
}
}
}
: 동기화가 필요한 메서드나 코드블럭 앞에 적어 동기화한다.
위 코드에서 synchronized가 걸려있는 함수는 setPrinterName과 realize로 모두 real을 변경할 수 있는 함수이다.
-> 자바는 멀티스레드 환경이므로 다른 스레드에서 개별적으로 real을 변경하려 하면서 PrinterProxy와 Printer의 name 값이 달라질 수 있는데 synchronized를 통해 이를 방지한다.
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.");
}
}
투과적이다라는 표현을 사용한다.)

대리인이 필요한 이유 [무거운 처리]
: Subject를 생성하는 데에 무거운 처리가 필요하므로 Subject의 생성 시점을 최대한 미루어 사용자의 스트레스를 줄인다.
ex. 문서 안에 그래픽 객체가 삽입되어 있을 때 그래픽 객체를 생성하는 것은 시간이 오래 걸리는 작업이므로 문서를 열 때 모든 그래픽 객체를 생성하는 대신, 각각의 그래픽 객체가 화면에 표시될 때 그래픽 객체를 생성한다.
대리인과 본인을 나눈 이유 [분할하여 통치하기]
: 둘을 분리하지 않고 지연 평가 기능을 넣을 수도 있으나 분리함으로써 개별적인 수정이 가능해진다.
- PrinterProxy 클래스의 구현을 바꾸어도 Printer 클래스를 수정할 필요가 없다.
- 지연평가를 할지 말지 결정을 PrinterProxy를 사용할지 말지를 통해 단순하게 구현이 가능하다.
: HTTP 서버와 HTTP 클라이언트 사이에서 웹 페이지 캐싱 등을 하는 소프트웨어
-> Clinet: 웹 브라우저, Proxy : HTTP 프록시, RealSubject : 웹 서버