본문 바로가기

웹 개발/Web Development

디자인 패턴) Proxy Pattern

이 글을 공부하며 번역하고 정리한 글입니다. 


타겟 객체에 대한 인터렉션을 가로채서 제어하는 패턴

프록시 객체를 사용하면 특정 객체와의 인터렉션에 대한 더 많은 제어권을 얻을 수 있습니다. 프록시 객체는 값을 가져오거나 설정할 때와 같이 객체와의 인터렉션 할 때의 동작을 결정할 수 있습니다.

 

일반적으로, 프록시는 '대리인'을 의미합니다. 누군가에게 직접 말하기보다는 그 누군가를 대표할만한 사람에게 대신 말하는 것이죠. Javascript에도 같은 상황이 있는 것입니다. 타겟 객체와 직접 상호작용하기보다는 대리인 객체 즉, 프록시 객체를 통해 타겟과 상호작용하는 것입니다. 

 


'John Doe'라는 인물을 표현하는 person 객체를 만들어봅시다. 

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American",
};

이 객체와 직접 상호작용하는 것이 아니라 프록시 객체를 통해 상호작용하려고 합니다. Javascript에서는 다음과 같이 Proxy라는 새로운 인스턴스를 만들어 프록시 객체를 쉽게 만들 수 있습니다. 

const personProxy = new Proxy(person, {});

Proxyd의 두 번째 인자오 들어온 {}는 핸들러 객체입니다. 핸들러 객체에 인터렉션 종류를 기반으로 특정 행위를 정의할 수 있습니다. 프록시 핸들러에는 더 다양한 메소드를 추가할 수 있지만 보통은 get, set 메소드를 가지고 있습니다. 

  • get: 객체의 속성에 접근하려고 할 때 호출하는 메소드
  • set: 객체의 속성을 수정하려고 할 때 호출하는 메소드

person 객체의 속성에 직접 접근하거나 값을 수정하지 않고 personProxy를 통해서 person 객체와 상호작용해야 합니다. 

 

personProxy에 핸들러를 추가해봅시다. 아래 코드에서 set은 속성을 수정할 때 호출되며, 이는 속성의 이전 값과 새로운 값을 콘솔에 출력합니다. get은 속성의 값과 속성 값을 가져오는데 사용한 키 값을 콘솔에 출력합니다. 

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
  },
});

프록시는 유효성 검증하는데 유용하게 사용됩니다. 사용자가 person 객체의 age라는 속성에 문자열을 넣으려고 하거나 name이라는 속성에 빈 값을 넣으려고 하는 경우, 또는 객체에 존재하지 않는 속성에 접근하려고 하는 경우 사용자에게 그 문제를 알려줘야 합니다. (그래서 TypeScript를 쓰는게 더 좋다..ㅎ) 

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        `Hmm.. this property doesn't seem to exist on the target object`
      );
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
  },
});

Reflect

JavaScript는 Reflect라고 불리는 빌트인 객체를 제공합니다. 이 객체는 프록시를 사용할 때 타겟 객체를 조작하기 쉽게 만들어줍니다. 

프록시 내에서 get, set 메소드를 통해 객체에 접근 및 수정하는 방법 대신에 Reflect를 사용할 수 있습니다. Reflect의 메소드는 핸들러 객체와 동일하게 get, set이라는 이름의 메소드를 가집니다.

핸들러 객체에서는 다음과 같이 타겟 객체에 접근하고 값을 수정합니다. 

* access: obj[prop] 
* modify: obj[prop] = value

Reflect 객체는 다음과 같습니다. 

* access: Reflect.get()
* modify: Reflect.set()

Reflect는 다음과 같이 구현하면 됩니다. 

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    Reflect.set(obj, prop, value);
  },
});

 

Tradeoffs

프록시는 객체를 제어할 수 있는 강력한 방법입니다. 프록시는 검증, 포맷팅, 알림창, 디버깅 등에 유용하게 사용됩니다. 

하지만 프록시 객체를 과도하게 사용하거나 핸들러 메소드가 과한 연산을 수행하는 것은 어플리케이션 성능에 좋지 않은 영향을 미칠 수 있습니다. 그래서 성능에 민감한 코드에는 프록시를 사용하지 않는 것이 좋습니다.