[Structural Patterns] - Proxy

Lee Jeong Min·2022년 1월 15일
0

디자인 패턴

목록 보기
13/23
post-thumbnail

의도

프록시는 클라이언트가 사용하는 실제 서비스 객체의 대체 역할을 하는 객체를 제공하는 구조 설계 패턴이다. 프록시는 클라이언트 요청을 수신하고 일부 작업을 수행한 후 요청을 서비스 객체로 전달한다.

프록시 객체는 서비스와 동일한 인터페이스를 가지고 있어 클라이언트에 전달될 때 실제 객체와 상호 호환이 가능하다.

문제

객체에 대한 액세스를 제어하려는 이유가 무엇인가? 방대한 양의 시스템 리소스를 소비하는 대규모 객체가 있다고 해보자.

데이터베이스 쿼리가 매우 느릴 수 있어서 액세스에 대한 제어가 필요하다.

객체가 실제로 필요할 때, 지연 초기화를 방법을 사용할 수 있다. 이렇게 되면 객체의 모든 클라이언트는 일부 지연 초기화 코드를 실행해야한다. 이는 아마 많은 코드의 중복으로 이어질 것이다.

이상적인 상황에서는 이 코드를 객체의 클래스에 직접 넣어 구현하면 되지만, 항상 가능한 것은 아니다. 예를 들어 클래스는 닫힌(오픈되지 않은) 타사 라이브러리의 일부일 수 있다.

해결책

프록시 패턴은 원래 서비스 객체와 동일한 인터페이스를 사용하여 새 프록시 클래스를 만들 것을 제안한다. 그런 다음 앱을 업데이트하여 프록시 객체를 원래 객체의 모든 클라이언트에 전달한다. 클라이언트의 요청을 받으면 프록시는 실제 서비스 객체를 생성하고 모든 작업을 해당 객체로 위임한다.

프록시는 데이터베이스 객체로 위장한다. 클라이언트나 실제 데이터베이스 객체도 모르게 느린 초기화 및 결과 캐싱을 중간에서 처리할 수 있다.

이렇게 하면 클래스를 변경하지 않고도 중간에 작업을 실행시킬 수 있다. 프록시는 원래 클래스와 동일한 인터페이스를 구현하므로 실제 서비스 객체를 사용하는 모든 클라이언트에 호환된다.

현실 유사성

신용카드는 현금과 마찬가지로 결제에 사용할 수 있다.

신용카드는 은행 계좌를 위한 프록시라고 볼 수 있다. 소비자는 현금을 많이 들고 다닐 필요가 없고, 점주 또한 거래로 얻은 수입이 현금이 아닌 통장에 전자적으로 추가돼 편리하게 돈을 관리할 수 있다.

구조

  1. 서비스 인터페이스는 서비스의 인터페이스를 선언한다. 프록시가 서비스 객체로 위장하려면 이 인터페이스를 따라야 한다.

  2. 서비스는 유용한 비즈니스 논리를 제공하는 클래스이다.

  3. 프록시 클래스에는 서비스 객체를 가리키는 참조 필드가 있다. 프록시가 처리를 마치면(예: 지연 초기화, 로깅, 액세스제어, 캐싱 등) 서비스 객체에 요청을 전달한다.
    일반적으로 프록시는 서비스 객체의 전체 수명 주기를 관리한다.

  4. 클라이언트는 동일한 인터페이스를 통해 서비스와 프록시를 모두 사용해야 한다. 이렇게 하면 서비스 객체를 필요로 하는 코드로 프록시를 전달할 수 있다.

적용가능성

  • 느리게 초기화(가상 프록시). 시스템 리소스를 항상 가동하여 낭비하는 가중치 높은 서비스 객체를 사용하는 경우

  • 액세스 제어(보호 프록시). 이는 특정 클라이언트에게만 서비스 객체를 사용할 수 있도록 하려는 경우

  • 원격 서비스의 로컬 실행(원격 프록시). 서비스 객체가 원격 서버에 있는 경우

  • 로깅 요청(로깅 프록시). 서비스 객체에 대한 요청 기록을 보관하려는 경우

  • 캐싱 요청 결과(캐싱 프록시). 특히 결과가 상당히 큰 경우 클라이언트 요청 결과를 캐싱하고 캐시의 수명 주기를 관리해야하는 경우

  • 현명한 참조. 이는 엄청 큰 객체를 사용하는 클라이언트가 없는 경우 해당 객체를 해제해야 하는 경우

장단점

장점

  • 클라이언트 모르게 서비스 객체를 제어할 수 있다.

  • 고객이 신경 쓰지 않을 때 서비스 객체의 수명 주기를 관리할 수 있다.

  • 서비스 객체가 준비되지 않았거나 사용할 수 없는 경우에 프록시가 작동한다.

  • OCP(열기/닫기 원리): 서비스나 클라이언트를 변경하지 않고 새 프록시 도입 가능

단점

  • 새로운 클래스를 많이 도입해야 하므로 코드의 복잡도 증가

  • 서비스 응답이 지연될 수 있다.

Proxy in TypeScript

TypeScript의 패턴 사용

복잡도: ★★☆

인기: ★☆☆

사용 예: 프록시 패턴이 대부분의 TS 응용 프로그램에서 자주 사용되는 것은 아니지만 일부 특별한 경우 매우 유용하다. 클라이언트 코드를 변경시키지 않고 일부 기존 클래스에 객체에 추가 동작을 생성하려는 경우 사용한다.

식별: 프록시는 모든 실제 작업을 다른 객체로 위임한다. 각 프록시 메서드는 프록시가 서비스의 하위 클래스가 아닌 한 서비스 객체를 참조해야 한다.

index.ts

// Subject 인터페이스는 RealSubject와 proxy간의 공통된 기능 인터페이스를 선언한다.
interface Subject {
  request(): void;
}

// RealSubject는 핵심 비즈니스 로직을 보유하고 있다.
class RealSubject implements Subject {
  public request(): void {
    console.log('RealSubject: Handling request.');
  }
}

// 프록시는 RealSubject와 동등한 인터페이스를 갖는다.
class Proxy1 implements Subject {
  private realSubject: RealSubject;

  // 프록시는 RealSubject 클래스 객체에 대한 참조를 유지한다.
  // 그것은 lazy-loaded 또는 클라이언트에 의한 프록시로의 통과를 할 수 있다.
  constructor(realSubject: RealSubject) {
    this.realSubject = realSubject;
  }

  // 대부분 프록시 패턴의 공통 어플리케이션은 레이지 로딩, 캐싱, 접근 제어, 로깅이다.
  public request(): void {
    if (this.checkAccess()) {
      this.realSubject.request();
      this.logAccess();
    }
  }

  private checkAccess(): boolean {
    console.log('Proxy: Checking access prior to firing a real request.');
    return true;
  }

  private logAccess(): void {
    console.log('Proxy: Logging the time of request.');
  }
}

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 Proxy1(realSubject);
clientCode(proxy);

결과

요약

프록시는 실제 서비스 객체의 대체 역할을 하는 객체를 제공하는 패턴
→ 실제 객체와 상호호환이 가능하다.

클래스를 변경하지 않고 중간에 작업을 실행시키는 경우 프록시 패턴을 사용

참고 사이트

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글