JavaScript 기준으로 설명합니다.
설정, 조회 등의 기본적인 객체 동작을 재정의하는 Proxy 객체를 활용한 패턴이다. 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조하는 것이 아니라 Proxy 객체를 통해 해당 객체에 접근한다.
프록시 패턴은 아래와 같은 특성을 갖는다.
- 원본 객체에 대한 접근을 제어한다.
- JavaScript에서는 후술할
Proxy
객체를 통해 주로 구현한다.
특정 객체를 감싸 객체에 가해지는 작업을 중간에서 가로채는 내장 객체이다. new Proxy(target, handler);
형태로 생성한다. 여기서 target
은 원본 객체이고, handler
는 프록시의 동작을 정의하는 객체이다. 빈 handler
로 생성된 프록시 객체는 원본 객체와 동일한 동작을 하며, get
, set
등의 메소드를 handler
에 추가하여 원본 객체의 속성에 접근하거나 수정할 수 있다. handler
는 원본 객체에 대한 호출을 잡아낸다는 의미에서 트랩이라고 불리기도 한다.
생성 시 주의할 점이 몇 가지 있다.
Proxy
를 더 간단하게 생성할 수 있게 하는 내장 객체이다. Proxy
와 달리 함수 객체가 아니므로 생성자로 사용되지 않는다. (new
연산자를 통해 생성하지 않는다.) Proxy handler
의 get()
, 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로 변경되었어요.
// ...
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. 특정 객체에 대한 접근 제한) 등의 다양한 구현 상황에 적용할 수 있다.
- 객체 생성 시 한 단계를 더 거치게 되므로, 빈번하게 생성되어야 하는 객체에 사용되었을 때 원본 객체를 직접 사용할 때보다 성능이 저하된다.
- 프록시 객체의 메서드 내에서 복잡한 작업이 발생하면 성능이 저하될 수 있다. (참고 문서)