Proxy As a Design Pattern
프록시는 일반적으로 다른 어떤 클래스의 인터페이스로 동작하는 클래스이다. (중략) 요컨대, 프록시는 내부적으로 실제의 객체(real subject)에 접근할 때 호출되는 래퍼(wrapper) 혹은 대리 객체다.
– 위키피디아(영문)
Introduction
상황을 가정해보자. 우선 다음과 같이 전화번호부 객체가 구현되어있는 상태이다.
1 | function PhoneBook() { |
이 전화번호부 클래스의 get()
이 호출될 때마다, 조회수를 따로 기록하여 저장하고 싶다면 어떡해야 할까? 전화번호부 클래스의 코드를 고치는 것은 예상치못한 사이드 이펙트를 초래할 수 있기 때문에 지양하고 싶다.
이런 경우에 프록시 패턴을 사용할 수 있다.
1 | function PhoneBookProxy() { |
위와 같이 프록시 객체를 구현해서 조회수를 따로 저장하고 PhoneBook
을 대리하는 객체로 PhoneBookProxy
를 사용하면 된다. 이 때 프록시 클래스는 공개된 인터페이스인 get()
메소드를 반드시 구현해야 한다.
Proxy for Caching
위의 PhoneBook
객체는 하나의 전화번호를 얻기 위해서는 무려 3초를 기다려야 한다. 따라서 캐시를 이용해 좀 더 빠르게 해볼 수 있을 것이다. 이 때도 프록시 패턴을 활용할 수 있다.
1 | function PhoneBookProxy() { |
아까 만든 프록시 객체에 캐시를 저장하는 변수를 추가하고, get()
메소드에 캐시를 검사하는 로직만 추가하면 된다. 프록시 객체를 통해서 get()
을 호출하면 2번째 요청부터는 캐시로부터 데이터를 반환할 수 있다.
$.proxy()
jQuery에서는 동명의 메소드를 구현하고 있다. jQuery의 $.proxy()
메소드는 함수 내부의 this
컨텍스트를 바꾸는 ES5의 bind()
와 비슷한 동작을 한다. 실제 jQuery의 구현 코드를 보면 컨텍스트를 바꿔주는 apply()
와 call()
을 사용하고 있는 걸 볼 수 있다. 하지만 컨텍스트를 바꾸어 주는 것 이외에는 원래 함수에 별다른 커스터마이징을 할 수가 없다. 원래 그런 용도가 아니기 때문.
여기에서는 오히려 특정 함수의 대상이 되는 객체가 프록시 된다. 따라서 앞서 소개한 프록시 패턴과는 다른 종류라고 볼 수 있다. 물론 의미적으로 프록시라는 말은 적절하다.
ES2015 Proxy
지원률이 낮아 당장 써먹긴 어렵지만, ES2015에도 Proxy
라는 타입이 추가되었다. 이 타입은 대상이 되는 객체에 대해서 JavaScript 문법 레벨의 요청(할당문 같은)조차도 가로챌 수 있는 강력한 기능을 제공한다. ES6 in depth에서 잘 설명하고 있다. 이걸 잘 이해하면 일반적인 프록시 디자인 패턴의 이해에도 도움이 될 것이다. 이 글에서 다루기엔 글이 너무 길어질 것 같아 추후에 다시 다뤄볼 예정이다.
마치며
프록시 패턴은 몇 가지 잘 알려진 유즈케이스가 있긴하지만, 넓게 보면 인터페이스가 동일한, 같은 결과를 반환하는 래퍼정도로 정의할 수 있을 것 같다. 이 글에서는 모든 유즈케이스를 다루지는 않았으니 아래의 참고링크를 참고해보면 도움이 될 것이다.(일반적인 다른 언어의 유즈케이스랑 크게 다르지 않음) 개인적으로는 학교에서 알량하게 배웠던 패턴을 다시금 나의 제 1언어가 된 JavaScript를 통해 다시 구현해보니 해당 패턴에 대한 이해도가 조금 높아진 것 같다.