Skip to content

Latest commit

 

History

History
144 lines (104 loc) · 6.05 KB

proxy-pattern.md

File metadata and controls

144 lines (104 loc) · 6.05 KB

Proxy Pattern

JavaScript 기준으로 설명합니다.

의미와 특성

설정, 조회 등의 기본적인 객체 동작을 재정의하는 Proxy 객체를 활용한 패턴이다. 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조하는 것이 아니라 Proxy 객체를 통해 해당 객체에 접근한다.

프록시 패턴은 아래와 같은 특성을 갖는다.

  • 원본 객체에 대한 접근을 제어한다.
  • JavaScript에서는 후술할 Proxy 객체를 통해 주로 구현한다.

JavaScript Proxy

특정 객체를 감싸 객체에 가해지는 작업을 중간에서 가로채는 내장 객체이다. new Proxy(target, handler); 형태로 생성한다. 여기서 target은 원본 객체이고, handler는 프록시의 동작을 정의하는 객체이다. 빈 handler로 생성된 프록시 객체는 원본 객체와 동일한 동작을 하며, get, set 등의 메소드를 handler에 추가하여 원본 객체의 속성에 접근하거나 수정할 수 있다. handler는 원본 객체에 대한 호출을 잡아낸다는 의미에서 트랩이라고 불리기도 한다.

생성 시 주의할 점이 몇 가지 있다.

Proxy를 더 간단하게 생성할 수 있게 하는 내장 객체이다. Proxy와 달리 함수 객체가 아니므로 생성자로 사용되지 않는다. (new 연산자를 통해 생성하지 않는다.) Proxy handlerget(), set() 등의 기본 메소드를 갖고 있으나, Proxy는 메소드를 직접 호출할 수 없는 것에 반해 Reflect는 가능하다.

Proxy handler 내부 동작들을 대체하는 예시를 살펴보자.

  • get() 메소드: obj[prop] -> Reflect.get(obj, prop)
  • set() 메소드: obj[prop] = value -> Reflect.set(obj, prop, value) 더 많은 예시는 이 문서에서 확인할 수 있다.

구현 예시

프록시 패턴으로 유효성 검사 구현하기

const profile = {
  displayName: "Zero",
  age: 20,
};

const validateProfile = (prop, value) => {
  if (prop === "displayName") {
    if (value.length < 2) {
      throw new Error(`이름을 2자 이상 입력해주세요.`);
    }
  } else if (prop === "age") {
    if (value < 5) {
      throw new Error(`5세 이상만 가입할 수 있어요.`);
    }
  }

  return true;
};

const profileProxy = new Proxy(profile, {
  get: (object, prop) => {
    if (!object[prop]) {
      throw new Error(`존재하지 않는 속성이에요.`);
    } else {
      console.log(`${prop}: ${object[prop]}`);
    }
  },
  set: (object, prop, value) => {
    if (!object[prop]) {
      throw new Error(`존재하지 않는 속성이에요.`);
    }

    if (validateProfile(prop, value)) {
      object[prop] = value;
      console.log(`${prop} 속성이 ${value}로 변경되었어요.`);

      return true;
    }
  },
});

// throw 시 이후 코드는 실행되지 않지만, 가독성을 위해 단순 나열했습니다.

// 조회
profileProxy.description; // → Error: 존재하지 않는 속성이에요.
profileProxy.displayName; // → displayName: Zero

// 수정
profileProxy.displayName = "!"; // → Error: 이름을 2자 이상 입력해주세요.
profileProxy.displayName = "제로"; // → displayName 속성이 제로로 변경되었어요.

profileProxy.age = 3; // → Error: 5세 이상만 가입할 수 있어요.
profileProxy.age = 21; // → age 속성이 21로 변경되었어요.

Reflect를 활용해 구현하기

// ...

const profileProxy = new Proxy(profile, {
  get: (object, prop) => {
    if (!Reflect.get(object, prop)) {
      throw new Error(`존재하지 않는 속성이에요.`);
    } else {
      console.log(`${prop}: ${Reflect.get(object, prop)}`);
    }
  },
  set: (object, prop, value) => {
    if (!Reflect.get(object, prop)) {
      throw new Error(`존재하지 않는 속성이에요.`);
    }

    if (validateProfile(prop, value)) {
      Reflect.set(object, prop, value);
      console.log(`${prop} 속성이 ${value}로 변경되었어요.`);

      return true;
    }
  },
});

// 조회
profileProxy.displayName; // → displayName: Zero

// 수정
profileProxy.displayName = "제로"; // → displayName 속성이 제로로 변경되었어요.
profileProxy.age = 21; // → age 속성이 21로 변경되었어요.

이 문서에서 프록시를 활용해 구현된 다양한 모듈들을 확인할 수 있다. 개인적으로 python-range가 가장 흥미로웠다.

장점

  • 원본 객체에 대한 접근을 제어하는 특성상 가상 프록시(ex. 이미지 지연 로드), 보호 프록시(ex. 특정 객체에 대한 접근 제한) 등의 다양한 구현 상황에 적용할 수 있다.

단점

  • 객체 생성 시 한 단계를 더 거치게 되므로, 빈번하게 생성되어야 하는 객체에 사용되었을 때 원본 객체를 직접 사용할 때보다 성능이 저하된다.
  • 프록시 객체의 메서드 내에서 복잡한 작업이 발생하면 성능이 저하될 수 있다. (참고 문서)

참고 자료