Proxy객체로 옵저버 패턴 만들기

MoonEn·2023년 2월 19일
1

JavaScript

목록 보기
5/5

과제 중에 Proxy 객체를 이용해서 옵저버 패턴을 구현하라는 요구가 있었다. Proxy 객체는 뭐고, 옵저버 패턴은 무엇인가? 이것을 찾고 구현하는데 꽤 오랜 시간이 걸렸다. 내가 이해한 방식이 맞는 지는 모르겠지만, 나중에 다시 찾을 때 쉽도록 내가 이해한 것과 구현한 방식을 기록한다.

옵저버 패턴

먼저 옵저버 패턴이 뭔지부터 이해를 해보자.

옵저버 패턴(Observer Pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등으로 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다.
(출처: 위키백과)

간단히 그림으로 표현하면 다음과 같다.
(이해한 대로 그려서 틀릴 수도 있음.)

Proxy 객체

그렇다면 Proxy 객체는 무엇일까?

Proxy 객체를 사용하면 한 객체에 대한 기본 작업을 가로채고 재정의하는 프록시를 만들 수 있습니다. Proxy 객체를 사용하면 원래 Object 대신 사용할 수 있는 객체를 만들지만, 이 객체의 속성 가져오기(get), 설정(set) 및 정의와 같은 기본 객체 작업을 재정의할 수 있습니다.

new Proxy(target, handler)

target: 프록시할 원복 객체
handler: 가로채는 작업과 가로채는 작업을 재정의하는 객체

(출처: mdn)

Proxy 객체를 사용하면 객체에 대한 기본 작업을 가로채고 재정의할 수 있다고 한다. 어떻게 하는 것인지 예시를 보자.

const target = {
  message1: "hello",
  message2: "everyone"
};
const handler3 = {
  get(target, prop, receiver) {
    if (prop === "message2") {
      return "world";
    }
    return Reflect.get(...arguments);
  },
};
const proxy3 = new Proxy(target, handler3);
console.log(proxy3.message1); // hello
console.log(proxy3.message2); // world(handler가 없으면 everyone 출력)

get의 매개 변수
target: 대상 객체
prop: property 가져올 속성의 이름
receiver: 프록시 또는 프록시에서 상속되는 객체

예시 코드를 보면, proxy3의 객체의 속성 값을 가져오기 위해서 proxy3.message1proxy3.message2를 썼다. 하지만 handler3get메서드로 작업을 가로챘다.

그렇다면 옵저버 패턴을 사용하기 위해서는 속성 값을 설정할 때 가로채면 될 것 같다.

Proxy로 옵저버 패턴 만들기

옵저버 패턴은 옵저버가 관찰 대상이 되는 객체의 상태 변화를 보고 기능을 수행하는 객체에 전달하는 방식이다. Proxy를 통하면 객체의 속성 값을 설정할 때, 동작을 가로채서 다른 객체가 동작하도록 만들 수 있다.

const target = {
    color: 'white',
};
const boxColorChange = (color) => {
    const $box = document.querySelector('.color-box');
    $box.style.backgroundColor = color;
};
const $btnList = document.querySelectorAll('.btn-color');
$btnList.forEach(($btn) => {
    $btn.addEventListener('click', () => {
        proxy.color = $btn.innerHTML;
    });
});
const proxy = new Proxy(target, {
    set(target, prop, value, receiver) {
        if (value === 'black') {
            console.log('검은색은 쓸 수 없습니다.');
            return true;
        }
        target[prop] = value;
        boxColorChange(value);
        console.log(target[prop]);
        return true;
    },
});

set의 매개변수
target: 대상 객체 (선언한 target 객체)
prop: property 가져올 속성의 이름 (target 객체의 color)
value: 설정할 속성 값 (button의 inner HTML)
receiver: 프록시 또는 프록시에서 상속되는 객체

위의 예시 코드는 target 객체의 color 값을 변경하면 boxColorChange함수가 동작하게 만들었다. 버튼을 누르면 proxy에 묶은 targetcolor 값을 변경하도록 이벤트리스너를 등록했고, target 객체의 값이 변경되기 때문에 set이 동작을 가로챈다. 동작을 가로챈 set 내부에 콜백함수를 넣어서 동작하게 만들었고, targetcolor에 값을 넣고 그 값을 출력하게 만들었다. 마지막으로 만약 value에 black이 들어오면 실행되지 않도록 하였다.

사실 이 경우 proxy를 안 쓰고도 간단하게 만들 수 있지만, 옵저버 패턴 예제를 위해서 proxy 객체를 사용해서 코드를 작성해 보았다.
(실제 이게 옵저버 패턴이 맞는지는 잘 모르겠다.)

(기타) Proxy 객체의 set 사용 예제

위의 예제 코드에서도 나왔지만, set을 사용하면 특정 객체의 값을 변경하는 것을 막을 수 있다. 그래서 함부로 변경하면 안되는 값이나, 특정 조건에 맞춰야 하는 값의 경우 임의로 변경하는 것을 막을 수 있다. mdn의 예제를 보자.

const monster1 = { eyeCount: 4 };
const handler1 = {
  set(obj, prop, value) {
    if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
      console.log('몬스터는 짝수의 눈만 가질 수 있습니다.');
    } else {
      return Reflect.set(...arguments);
    }
  }
};
const proxy1 = new Proxy(monster1, handler1);
proxy1.eyeCount = 1;
// Expected output: "몬스터는 짝수의 눈만 가질 수 있습니다."
console.log(proxy1.eyeCount);
// Expected output: 4
proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);
// Expected output: 2

monster1eyeCount를 변경하는데 짝수만 입력할 수 있게 만들어 놓았다. 이렇게 하면 홀수로 잘못 입력하는 것을 방지할 수 있다.

const target = {
    _donotChange : '바꾸지 마세요',
    canBeChanged: '바꿀 수 있어요'
}
const handler = {
    set(target, prop, value, receive){
        if(prop.includes('_')) {
            console.log('바꿀 수 없습니다.')
        } else {
            return Reflect.set(...arguments)
        }
    }
}
const proxy = new Proxy(target, handler)
proxy._donotChange = '바꾸고 싶다.'
// Expected Output: '바꿀 수 없습니다.'
proxy.canBeChanged = '이건 바꿀 수 있다.'
console.log(proxy.canBeChanged)
// Expected Output: '이건 바꿀 수 있다.'

그리고 위의 예시 코드처럼 언더바(_)를 사용해서 값을 임의로 변경하는 것을 막을 수도 있다.(언더바는 변경하면 안되는 변수에 관습적으로 사용한다고 들었다.)

마무리

Proxy 객체를 이용해서 옵저버 패턴을 만들기 위해서 이 문서, 저 문서를 뒤졌으나 이렇게 만들면 된다라는 문서를 발견하지 못했다. 그래서 이리저리 테스트 해보던 중 Proxy 객체에 대해서 이해가 되는 부분이 있어서 글로 남겼다. 이 문서가 100% 맞다고 보장할 수는 없지만, 향후 내가 다시 Proxy 객체에 대해서 공부하기 위해서 다시 이 문서를 볼 것은 100% 사실이라고 생각한다. 그 때 내가 이해할 수 있도록 자세히 풀어썼다. 이렇게 글로 정리하니 이해한 내용이 좀 더 잘 정리가 되었다.
혹시라도 이 문서 틀린 내용이 있고, 좀 더 보충해야 하는 내용이 있다면 꼭 댓글로 알려주길 바란다. 잘 모르는 걸 풀어써서 틀린 것이 많을 것이라 예상한다. 그래도 귀엽게 봐주길...

profile
개발자를 꿈꾸는 직장인

0개의 댓글