Observer Pattern 에 대하여

Observer Pattern 에 대하여

지난 포스팅에 이어서, 오늘은 옵저버 패턴에 대해서 알아보도록 하겠습니다.

Observer Pattern

옵저버 패턴은 특정 객체의 상태 변화를 감지하고,

변화가 발생할 때마다 등록된 옵저버들에게 이를 자동으로 알리는 디자인 패턴입니다.

상태 변화를 감시하는 중심 객체가 있고,

해당 상태변화를 기반으로 추가적인 동작을 수행하는 객체인 옵저버가 존재합니다.

옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며,
MVC(Model - View - Controller) 패턴에 사용됩니다.

예를 들어 모델의 데이터가 변경되면 이를 감시하고 있던 뷰에게 자동으로 변경 사항이 전달됩니다. 그에 따라 컨트롤러가 추가적인 로직을 수행하는 방식으로 동작하게 됩니다.

이를 통해 구성 요소간의 결합도를 낮추면서도, 효율적으로 상태 변화를 반영할 수 있습니다.

이 옵저버 패턴을 트위터 같은 SNS 로 생각하면 이해가 좀 더 쉽습니다.

어떤 사용자를 팔로우 했다면, 주체가 게시글을 올리면 알림이 팔로워에게 가는 구조를 떠올리시면 됩니다.

자바스크립트에서의 옵저버 패턴

Proxy 객체

자바스크립트에서의 옵저버 패턴은 프록시 객체를 통해 구현할 수도 있습니다.

프록시 객체는 특정 객체의 동작(속성 접근, 변경, 함수 호출 등)을 가로채 원하는 로직을 삽입할 수 있도록 해주는 기능입니다.

웹 프록시 같은 것을 생각하면 됩니다.

이를 통해 객체의 속성에 접근할 때마다 특정 동작을 수행하거나, 상태 변화를 감지할 수 있습니다.

Proxy 객체의 두 가지 주요 매개변수

  1. target -> 감싸고 있는 실제 객체
  2. handler -> 객체의 동작을 가로채 실행할 함수

코드를 통해 예제를 살펴봅시다.

const handler = {
    get: (target, property) => {
        return property === 'fullName' ? `${target.firstName} ${target.lastName}` : target[property];
    }
};

const person = new Proxy({ firstName: 'John', lastName: 'Doe' }, handler);

console.log(person.fullName); // "John Doe"

new Proxy() 로 firstName 과 lastName 을 가진 객체 person 을 선언했습니다.

이후 person 의 fullName 을 참조하면 기존에는 없던 속성인 'fullName'을 확인할 수 있습니다.

만약 어디에선가 person 객체의 fullName에 접근한다면, 해당 속성이 생기게 되는 것이죠.

이렇게 어떠한 부분을 가로채 로직을 강제할 수 있는 것이 프록시 객체입니다.

Proxy 객체를 이용한 옵저버 패턴 구현

위에서 설명한 자바스크립트의 Proxy 객체를 이용해서 옵저버 패턴을 구현해보겠습니다.

웹 애플리케이션에서는 사용자의 상태 변경을 추적하거나 데이터가 변경될 때 특정 로직을 실행해야 하는 경우가 많습니다.


예를 들어, 사용자의 프로필 정보를 감시하고 변경이 발생할 때 알림을 보내는 방식으로 옵저버 패턴을 활용할 수 있습니다.

function createReactiveUser(user, onChange) {
    return new Proxy(user, {
        set(target, property, value) {
            if (target[property] !== value) {
                const prevValue = target[property];
                target[property] = value;
                onChange(`[${property}] 속성이 [${prevValue}] → [${value}]로 변경되었습니다.`);
            }
            return true;
        }
    });
}

// 사용자 데이터 객체
const user = {
    name: "eunbi",
    status: "offline"
};

// 사용자 상태 변화를 감지하는 프록시 객체 생성
const reactiveUser = createReactiveUser(user, console.log);

// 상태 변경 테스트
reactiveUser.status = "online";  
// 출력: [status] 속성이 [offline] → [online]로 변경되었습니다.

reactiveUser.name = "Bob";  
// 출력: [name] 속성이 [Alice] → [Bob]로 변경되었습니다.

위 코드를 살펴보면,
createReactiveUser() 함수는 프록시 객체를 생성하여 속성이 변경될 때마다 onChange 콜백 함수를 호출합니다.

set() 을 이용하여 객체 속성 변경을 감지하고, 이전 값과 새로운 값을 비교한 후 변경 사항을 기록합니다.

reactiveUser 객체를 통해 사용자의 이름이나 상태를 변경하면 자동으로 로그가 찍히게 됩니다.

프록시 객체의 get() 함수는 속성과 함수에 대한 접근을 가로챕니다.

set() 함수는 속성에 대한 접근을 가로채고, has() 함수는 in 연산자의 사용을 가로 챕니다.

위 코드에서는 set() 을 이용하여 속성이 set(변경)될 때 가로챘음을 알 수 있습니다.

마무리

저 역시 실무에서 특정 이벤트가 발생하면 여러 리스너(옵저버)가 이를 감지하고 반응해야 하는 로직을 구현해야 했던 경험이 있습니다.

비록 저는 Proxy 대신 EventEmitter 패키지를 활용했지만, 다양한 상황에서 옵저버 패턴을 적용할 일이 많다는 것을 몸소 느꼈습니다.

특히 이벤트 기반 아키텍처를 설계할 때, 이 패턴을 이해하고 있으면 훨씬 더 유연하고 확장성 있는 시스템을 구축할 수 있습니다.

오늘은 옵저버 패턴의 개념과, 실제 자바스크립트로 구현한 옵저버 패턴을 살펴보았습니다.

이 글이 도움이 되셨다면 좋겠습니다.