Observer pattern (JavaScript)

Image copied from : http://www.deepakkamboj.com/design-patterns-for-software-developer-and-architect/

개요

MVC(Model-View-Controller)패턴을 이용하여 프로젝트를 개발하던 중, 상태(Model)가 변경되면, 화면(View)이 리렌더링되도록 해야하는 부분이 있었다.

단순히 상태가 변경될 때, htmlrender하는 함수를 호출하면 되지만,

그렇게 되면 Model과 호출될 render함수의 종속성이 강해지기도 하고,

잘못 설계하는 경우 ModelView사이가 상호 참조가 될 가능성이 있기 때문에 결론적으로 옵저버 패턴을 사용하기로 결정하였다.

옵저버 패턴에 대한 이해도를 높이기 위해 포스팅을 남기고, 이를 간단하게 구현해보도록 하겠다.


이벤트 드리븐 프로그래밍

웹 애플리케이션을 개발하려면, 애플리케이션 구동중에 비동기적으로 발생할 다양한 사용자 이벤트(click, keyup, …)에 대해 미리 고려하고, 브라우저가 이를 감지하고 적절한 동작을 수행할 수 있도록 설계해야 한다.

그리고 이렇게 이벤트에 반응하여, 동적으로 동작이 수행되도록 하는 프로그래밍 방식을 이벤트 드리븐(event-driven) 프로그래밍이라고 한다.


그런데, 기본적으로 이벤트는 비동기적으로 발생한다.

즉, 개발자는 사용자가 언제 애플리케이션과 상호작용(interaction)할지 모른다는 뜻이다.

이를 해결하기 위해, JavaScript에서는 addEventListener 또는 DOM 요소의 이벤트 핸들러 프로퍼티handler를 바인딩하는 방식으로 브라우저에게 이벤트 감지를 맡기고, 동작 수행을 위임한다.


상태 변화 감지

위와 같은 이벤트 드리븐 방식이 바로 옵저버 패턴(Observer pattern)이 적용된 대표적인 예 인데, 이처럼 상태변화에 대해서 관찰자(observer)가 상태변화를 관찰(observe)하게 하고, 변화가 생길때 마다 관찰자(observer)들에게 변경사항이 있음을 알려주는 것이 옵저버 패턴이다.



Observer pattern

옵서버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다 - Wikipedia, 옵저버 패턴

위키백과에서는 옵저버 패턴을 위와 같이 정의하고 있다.

이를 구체화하여 다이어 그램으로 나타내면 아래와 같다.

출처 : Wikipedia, 옵저버 패턴

위 다이어그램은 객체지향을 기반으로 작성된 다이어그램이다.

객체지향을 조금 덜어내고, JavaScript 기반의 MVC형태로 간소화한다면, 아래와 같이 나타낼 수 있을 것 같다.

위 다이어그램을 보면, 구현에 따라 Model을 참조하고 있는 View 또는 Controller에서 observer로 등록할 함수(메서드)를 subscribe(listener)로 등록하게 된다.

그리고 Model에서는 상태변경이 일어날 때 마다 notify() 메서드를 호출하여, 상태가 변경되었음을 observer에 알려 observer 함수가 후처리되도록 구현할 수 있다.

이벤트 핸들러의 호출을 브라우저에게 위임하는 것과 같이 observer의 호출을 상태(model) 객체에게 위임한다고 이해하면 더 쉽게 이해가 가능할 것 같다.
첨언하자면 개인적으로 View에서 observer를 등록하는 것이 맞다고 생각한다.



구현

이를 기반으로 TODO - 할일 목록 관리 프로그램을 만든다고 생각해보자.

TODO객체가 갱신될 때 마다, View 또한 함께 갱신되어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Model
const store = {
state: {
todos: [],
},

// observer
listeners: [],
notify() {
this.listeners.forEach(listener => listener(this.state));
},

get todos() {
return this.state.todos;
},
set todos(newTodos) {
this.state.todos = newTodos;
this.notify(); // noitfy to observers
},
};

// observer pattern
const subscribe = listener => {
store.listeners.push(listener);
};

이렇게 구현하면, state(상태)가 갱신될 때마다, listeners(observers)에 등록된 함수들이 호출되게 될 것 이다.

이제 html을 렌더링하는 함수 render()가 있다면, subscribe(render)와 같이 등록하여, observer 패턴이 정상적으로 동작하게 할 수 있다.


참고