JavaScript 프록시(Proxy)

Proxy As a Design Pattern

프록시는 일반적으로 다른 어떤 클래스의 인터페이스로 동작하는 클래스이다. (중략) 요컨대, 프록시는 내부적으로 실제의 객체(real subject)에 접근할 때 호출되는 래퍼(wrapper) 혹은 대리 객체다.
위키피디아(영문)

Introduction

상황을 가정해보자. 우선 다음과 같이 전화번호부 객체가 구현되어있는 상태이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function PhoneBook() {
this.dictionary = {
'이승민': '01012341234',
'이현섭': '01023456789',
'오유근': '01077777777'
};
}

PhoneBook.prototype.get = function(name, callback) {
var self = this;
setTimeout(function() {
callback(self.dictionary[name]);
}, 3000);
}

이 전화번호부 클래스의 get()이 호출될 때마다, 조회수를 따로 기록하여 저장하고 싶다면 어떡해야 할까? 전화번호부 클래스의 코드를 고치는 것은 예상치못한 사이드 이펙트를 초래할 수 있기 때문에 지양하고 싶다.

이런 경우에 프록시 패턴을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function PhoneBookProxy() {
var phoneBook = new PhoneBook();
var viewCount = 0;

return {
get: function(name, callback) {
viewCount++;
phoneBook.get(name, callback);
},

getViewCount: function() {
return viewCount;
}
};
}

위와 같이 프록시 객체를 구현해서 조회수를 따로 저장하고 PhoneBook을 대리하는 객체로 PhoneBookProxy를 사용하면 된다. 이 때 프록시 클래스는 공개된 인터페이스인 get()메소드를 반드시 구현해야 한다.

Proxy for Caching

위의 PhoneBook 객체는 하나의 전화번호를 얻기 위해서는 무려 3초를 기다려야 한다. 따라서 캐시를 이용해 좀 더 빠르게 해볼 수 있을 것이다. 이 때도 프록시 패턴을 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function PhoneBookProxy() {
var phoneBook = new PhoneBook();
var viewCount = 0;
var cache = {};

return {
get: function(name, callback) {
viewCount++;

if (cache[name]) {
callback(cache[name]);
} else {
phoneBook.get(name, function(number) {
cache[name] = number;
callback(number);
});
}
},

getViewCount: function() {
return viewCount;
}
};
}

아까 만든 프록시 객체에 캐시를 저장하는 변수를 추가하고, get() 메소드에 캐시를 검사하는 로직만 추가하면 된다. 프록시 객체를 통해서 get()을 호출하면 2번째 요청부터는 캐시로부터 데이터를 반환할 수 있다.

$.proxy()

jQuery에서는 동명의 메소드를 구현하고 있다. jQuery의 $.proxy() 메소드는 함수 내부의 this 컨텍스트를 바꾸는 ES5의 bind()와 비슷한 동작을 한다. 실제 jQuery의 구현 코드를 보면 컨텍스트를 바꿔주는 apply()call()을 사용하고 있는 걸 볼 수 있다. 하지만 컨텍스트를 바꾸어 주는 것 이외에는 원래 함수에 별다른 커스터마이징을 할 수가 없다. 원래 그런 용도가 아니기 때문.
여기에서는 오히려 특정 함수의 대상이 되는 객체가 프록시 된다. 따라서 앞서 소개한 프록시 패턴과는 다른 종류라고 볼 수 있다. 물론 의미적으로 프록시라는 말은 적절하다.

ES2015 Proxy

지원률이 낮아 당장 써먹긴 어렵지만, ES2015에도 Proxy라는 타입이 추가되었다. 이 타입은 대상이 되는 객체에 대해서 JavaScript 문법 레벨의 요청(할당문 같은)조차도 가로챌 수 있는 강력한 기능을 제공한다. ES6 in depth에서 잘 설명하고 있다. 이걸 잘 이해하면 일반적인 프록시 디자인 패턴의 이해에도 도움이 될 것이다. 이 글에서 다루기엔 글이 너무 길어질 것 같아 추후에 다시 다뤄볼 예정이다.

마치며

프록시 패턴은 몇 가지 잘 알려진 유즈케이스가 있긴하지만, 넓게 보면 인터페이스가 동일한, 같은 결과를 반환하는 래퍼정도로 정의할 수 있을 것 같다. 이 글에서는 모든 유즈케이스를 다루지는 않았으니 아래의 참고링크를 참고해보면 도움이 될 것이다.(일반적인 다른 언어의 유즈케이스랑 크게 다르지 않음) 개인적으로는 학교에서 알량하게 배웠던 패턴을 다시금 나의 제 1언어가 된 JavaScript를 통해 다시 구현해보니 해당 패턴에 대한 이해도가 조금 높아진 것 같다.

참고 링크