Dahyee 2023. 3. 14. 19:47

Proxy Pattern : 디자인패턴 중 하나. 

 

Proxy는 객체를 지켜보다가 객체에 특정한 행동을 할 때 (getter로 필드 값을 가져오거나 setter로 필드 값을 설정하거나 constructure로 인스턴스를 생성하거나) 설정해놓은 핸들러(메소드)를 거치게 하는 스펙이다. 

 

간단한 예로 person이라는 객체가 있고 그 객체를 프록시로 감싸보자.

const person = {name:'Heather', age:29}

const proxyPerson = new Proxy(person, {
	// 여기서 원하는 행동을 선언해준다.
}

첫번째 인자로는 지켜보고자 하는 객체를 받고 두번째 인자로는 핸들러 객체를 받는다. 

핸들러 객체에는 내장 메소드가 정해져 있다. 

간단한 get과 set을 써보자

const person = {name:'Heather', age:29}

const proxyPerson = new Proxy(person, {
	get:(target, property)=>{
    		console.log(`you're trying to get ${property}`)
        	return target[property]
        }
}

const output = proxyPerson.name // 'you're trying to get name'
console.log(output) // Heather

get 메소드를 사용하면 해당 객체의 필드값을 getter를 통해 불러올 때마다 get메소드 안에 있는 함수가 호출되고 return 된 값이 getter의 값으로 반환된다.

 

const person = {name:'Heather', age:29}

const proxyPerson = new Proxy(person, {
	set:(target, property, value)=>{
    		console.log(`you're trying to set ${property}`)
        	target[property] = value;
            return true;
        }
}

proxyPerson.age = 30 // 'you're trying to set age'
console.log(proxyPerson.age) // 30

set 메소드를 사용하면 해당 객체의 필드값을 setter를 통해 설정할 때마다 set 메소드 안에 있는 함수가 호출된다.

set 메소드 안에서 성공적으로 setter가 되었다는 의미의 true를 반환해줘야 한다. 

 

 

오호... 객체가 특정행동을 할 때 감시하고 있다가 필요한 함수를 호출해줄 수 있구만.

아래 클래스에서 모든 메소드가 saveDate를 호출하고 있는데 그렇다면 클래스 객체를 프록시로 감싸서 감시하고 있다가 메소드가 호출 될때마다 saveData를 호출해주면 좋지 않을까? 

라는 고민에서 Proxy공부가 시작되었다. 

import {saveData} from '../util/localstorage'

class RestaurantListHandler {
  private restaurants: Restaurant[] = [];

  constructor() {
    this.restaurants = getSavedData(Constants.RESTAURANT_LIST);
  }

  getTotalRestaurants() {
    return this.restaurants;
  }

  addRestaurant(restaurant: Restaurant) {
    this.restaurants = [restaurant, ...this.restaurants];
    
    saveData('restaurantList',this.restaurants);
  }

  deleteRestaurant(id: string) {
    this.restaurants = this.restaurants.filter(
      (restaurant) => restaurant.id !== id
    );
    
    saveData('restaurantList',this.restaurants);
  }

  toggleBookmark(id: string) {
    this.restaurants = this.restaurants.map((restaurant) => {
      if (restaurant.id === id) {
        return { ...restaurant, bookmarked: !restaurant.bookmarked };
      }
      return restaurant;
    });
    
    saveData('restaurantList',this.restaurants);
  }
}

물론 getRestaurant 메소드에서는 saveData를 호출하지 않지만 뭐 한번쯤 더해도 되지..

 

구현해보자 프록시

const proxyRestaurantHandler = new Proxy(RestaurnatListHandler, {
	apply:(target, thisArg, Args)=>{
    	RestaurnatListHanlder[target](Args);
        saveData('restaurantList', thisArg.restaurants);
    	}
}

proxyRestaurantHandler.deleteRestaurant('id123');

apply가 함수호출할때마다 실행되는 메소드이니까 이렇게 구현하면 되겠지? 

 

음? 안되네 뭐가 문제지....

하고 알아보니 apply 메소드는 new Proxy의 첫번째 인자로 함수가 들어올 때, 그 함수가 호출될 때 사용할 수 있는 메소드였다... 또르륵...

 

첫번째 인자로 객체(메소드와 필드를 가진)를 받고, 그 객체의 메소드가 호출될 때마다 proxy apply 메소드가 호출될 수는 없단 말인가?

없다..

 

할 수 있는 방법은 메소드 하나하나를 proxy로 감싸서 그 안에서 saveData를 호출해주는 방식인데 그럴러면 뭐하러 쓰지? ㅎㅎ 그래서 안썼다.

 

덕분에 proxy 공부 할 수 있었다. 

Proxy는 validation 등 특정동작이 모든 getter와 setter상황에서 필요할 때 유용하다. 

모든 getter와 setter에서 필요없다면 if문으로 제어해주면 되겠지만 그렇게까지 해서 프록시를 사용해야하는 이유는 모르겠다. 

그래도 함수가 단일책임의 원칙을 지키게 할 수 있는 좋은 방법인 것 같기도 하다. 

 

apply 사용 예시를 마지막으로 작성해보았다.

const sum = (a, b) => {
  return a + b;
}

const proxySum = new Proxy(sum, {
  apply: function(target, thisArg, argumentsList) {
    console.log(`arguments: ${argumentsList}`);
    const result = target.apply(thisArg, argumentsList);
    console.log(`returned: ${result}`);
    return result;
  }
});

proxySum(1, 2); // Logs "arguments: 1,2" and "returned: 3"