다른 객체의 대리자나 placeholder를 제공해주는 구조 패턴
객체에 대한 접근을 제어하는 이유 - 많은 시스템 리소스를 소모하는 거대한 객체가 있다고 가정

프록시 패턴 - 원본 서비스 객와 같은 인터페이스를 가지는 새로운 프록시 클래스 생성 → 모든 원본 객체의 클라이언트들에게 프록시 객체를 전달하게 함
클라이언트로부터 요청을 받으면 프록시가 실제 서비스 객체를 만들어 모든 작업을 위임

프록시가 클래스를 변경하지 않고도 클래스의 주요 로직 전후로 무언가를 실행할 수 있게 해줌
프록시는 원본 객체와 같은 인터페이스를 구현하기 때문에 실제 서비스 객체를 사용하는 어떤 클라이언트에게든 전달될 수 있음

1. 서비스 인터페이스 - 서비스의 인터페이스 선언, 프록시는 이 인터페이스를 따라야만 서비스 객체로 위장 가능
2. 서비스 - 비즈니스 로직을 제공하는 클래스
3. 프록시 클래스 - 서비스 객체의 참조 필드 보유, 프록시가 처리
(e.g. 지연 초기화, 로깅, 접근 제어, 캐싱 등등)를 완료하면 요청을 서비스 객체에 전달
- 프록시는 보통 서비스 객체들의 전체 생명 주기를 관리
4. 클라이언트 - 같은 인터페이스를 통해 서비스와 프록시 모두를 다룸
→ 서비스 객체를 사용하는 어떤 코드에도 프록시 전달 가능
- 앱 구동 시 객체를 생성하는 대신, 정말 필요할 때까지 객체의 초기화를 미룰 수 있음
- 클라이언트의 자격 증명이 특정 조건에 부합하는 경우에만 프록시가 서비스 객체에 요청 전달
e.g. 객체 - OS의 중요한 부분, 클라이언트 - 실행 중인 응용 프로그램 (악의적 프로그램 포함)
- 프록시가 네트워크를 통해 클라이언트 요청을 전달해 네트워크를 다루는 복잡한 작업 처리
- 프록시가 서비스에 각 요청을 전달하기 전에 기록함
- 프록시는 항상 같은 결과를 생성하는 반복 요청들을 캐시할 수 있음
- 요청의 파라미터를 캐시 키로 사용 가능
- 프록시는 서비스 객체에 대한 참조나 결과를 얻은 클라이언트들을 추적할 수 있음
- 클라이언트들이 아직 active한지 주기적으로 확인하고,
클라이언트 리스트가 비어 있으면 프록시가 서비스 객체를 해제해 시스템 자원 확보
- 프록시는 클라이언트가 서비스 객체를 변경했는지도 추적 가능
→ 변경되지 않은 객체는 다른 클라이언트에 의해 재사용 가능
1. 기존 서비스 인터페이스가 없다면 만들어서 프록시와 서비스 객체를 상호 교환 가능하게 만듦
- 서비스의 모든 클라이언트들이 해당 인터페이스를 사용하도록 변경해야 하기 때문에
서비스 클래스의 인터페이스를 추출하는 것이 항상 가능하진 않음
→ 플랜 B는 프록시를 서비스 클래스의 서브클래스로 만들어 서비스의 인터페이스를 상속받게 하는 것
2. 프록시 클래스 생성
- 서비스에 대한 참조를 저장할 필드 필요
- 대개 프록시는 서비스를 생성하고 서비스의 전체 생명 주기를 관리함
- 드물게는 클라이언트가 생성자를 통해 서비스를 프록시에게 전달할 수도 있음
3. 프록시 메서드를 목적에 맞게 구현
- 대부분의 경우, 프록시는 특정 작업을 완료한 후 작업을 서비스 객체에 위임해야 함
4. (optional) 클라이언트가 프록시를 받을 지 실제 서비스를 받을 지 결정하는 생성 메서드 도입
- 프록시 클래스 내부의 간단한 정적 메서드가 될 수도 있고, 완전한 팩토리 메서드가 될 수도 있음
5. (optional) 서비스 객체에 지연 초기화 구현
- 서비스 객체를 클라이언트가 알지 못하게 제어 가능
- 클라이언트가 신경쓰지 않을 때 서비스 객체의 생명 주기 관리 가능
- 프록시는 서비스 객체가 준비되지 않았거나 사용할 수 없을 때에도 작동함
- OCP - 서비스나 클라이언트를 변경하지 않고 새로운 프록시 도입 가능
- 여러 새로운 클래스들을 도입해야 해서 코드 복잡도 증가
- 서비스로부터의 응답이 지연될 수 있음
- 어댑터 - 래핑된 객체에 다른 인터페이스 제공,
프록시 - 같은 인터페이스 제공,
데코레이터 - 향상된 인터페이스 제공
- 퍼사드와 프록시는 둘 다 복잡한 객체를 완화하고 자체적으로 초기화한다는 점에서 비슷하지만,
프록시는 퍼사드와 달리 서비스 객체와 같은 인터페이스를 가지기 때문에 상호 교환 가능
- 데코레이터와 프록시는 비슷한 구조를 가지지만 매우 다른 의도를 가짐
- 두 패턴 모두 한 객체가 일부 작업을 다른 객체에 위임하는 합성 원칙 기반
- 프록시 - 서비스 객체의 생명 주기를 주로 직접 관리
- 데코레이터 - 합성이 클라이언트에 의해 제어됨
/**
* The Subject interface declares common operations for both RealSubject and the
* Proxy. As long as the client works with RealSubject using this interface,
* you'll be able to pass it a proxy instead of a real subject.
*/
interface Subject {
request(): void;
}
/**
* The RealSubject contains some core business logic. Usually, RealSubjects are
* capable of doing some useful work which may also be very slow or sensitive -
* e.g. correcting input data. A Proxy can solve these issues without any
* changes to the RealSubject's code.
*/
class RealSubject implements Subject {
public request(): void {
console.log('RealSubject: Handling request.');
}
}
/**
* The Proxy has an interface identical to the RealSubject.
*/
class Proxy implements Subject {
private realSubject: RealSubject;
/**
* The Proxy maintains a reference to an object of the RealSubject class. It
* can be either lazy-loaded or passed to the Proxy by the client.
*/
constructor(realSubject: RealSubject) {
this.realSubject = realSubject;
}
/**
* The most common applications of the Proxy pattern are lazy loading,
* caching, controlling the access, logging, etc. A Proxy can perform one of
* these things and then, depending on the result, pass the execution to the
* same method in a linked RealSubject object.
*/
public request(): void {
if (this.checkAccess()) {
this.realSubject.request();
this.logAccess();
}
}
private checkAccess(): boolean {
// Some real checks should go here.
console.log('Proxy: Checking access prior to firing a real request.');
return true;
}
private logAccess(): void {
console.log('Proxy: Logging the time of request.');
}
}
/**
* The client code is supposed to work with all objects (both subjects and
* proxies) via the Subject interface in order to support both real subjects and
* proxies. In real life, however, clients mostly work with their real subjects
* directly. In this case, to implement the pattern more easily, you can extend
* your proxy from the real subject's class.
*/
function clientCode(subject: Subject) {
// ...
subject.request();
// ...
}
console.log('Client: Executing the client code with a real subject:');
const realSubject = new RealSubject();
clientCode(realSubject);
console.log('');
console.log('Client: Executing the same client code with a proxy:');
const proxy = new Proxy(realSubject);
clientCode(proxy);
// Output.txt
Client: Executing the client code with a real subject:
RealSubject: Handling request.
Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.
참고 자료: Refactoring.guru