이번 주는 옵저버 패턴을 구현하여 자바스크립트 프로젝트에 적용해 보는 시간을 가졌다. 추상적으로만 알고 있던 개념을 직접 클래스나 함수와 같은 실질적인 형태로 구현해 보니 훨씬 잘 이해가 되었다.
옵저버 패턴(observer pattern)은 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. - 위키백과
위 그림은 내가 이해한 옵저버 패턴의 구조를 그림판으로 그려본 것이다. 복잡한 개념 같지만 결국 상태값을 변화시키는 쪽(주로 이벤트)과 상태값을 이용하는 쪽(렌더 함수 등)으로 나뉘는 것뿐이다!
만약 옵저버 패턴을 사용하지 않는다면 상태를 변화시키는 쪽에서 다음에 수행해야 할 작업들을 직접 정의하고 처리해야 할 것이다. 왜냐하면 상태값을 받아서 사용만 하는 쪽에서는 그 변화를 스스로 알아챌 능력이 없기 때문이다.
예를 들면 카운트 변수가 있고, 그것을 화면에 그려주는 렌더 함수와, 클릭할 때마다 카운트를 1씩 증가시키는 버튼 요소가 있다고 하자. 코드로 보면 대충 이렇게 될 것이다.
let count = 0;
function renderCount() {
$('#current-count').textContent = count;
}
function increaseCount() {
count += 1;
}
$('#increase-count-button').addEventListener('click', increaseCount);
renderCount();
위 코드를 실행하면 카운트 값은 최초 1회만 ‘0’으로 렌더링되고, 버튼을 아무리 누르더라도 (렌더 함수를 따로 실행시키지 않으므로) 바뀌는 것이 없을 것이다. 그러므로 이런 상황에서는 이벤트를 처리하는 핸들러 쪽에서 직접 렌더 함수를 호출해 주어야 한다.
function increaseCount() {
count += 1;
renderCount(); // 상태를 변화시킨 쪽에서 직접 재호출
}
반면, 옵저버 패턴을 이용하면 코드를 이런 식으로 바꿀 수 있다.
let count = new ObservableCount(0);
function renderCount() {
$('#current-count').textContent = count;
}
function increaseCount() {
count.setValue(count.getValue() + 1); // setValue() 내부에서 notify()를 호출함
}
$('#increase-count-button').addEventListener('click', increaseCount);
ObservableCount.subscribe(renderCount);
카운트 변수가 Observable 객체로 선언되었고, 값을 변화시키는 함수는 옵저버블의 메서드를 이용하도록 하여 내부에서 notify()를 호출하도록 하였다. 또, 렌더 함수가 카운트 변수를 '구독'하도록 했다. (즉, 옵저버 목록에 추가)
이제 버튼을 클릭하여 카운트가 증가할 때마다 알아서 렌더 함수가 실행되면서 카운트 값이 정상적으로 표시될 것이다. 상태를 변화시키는 쪽에서 이제 렌더링을 신경 쓸 필요 없이 순수하게 '상태 변경'에만 집중하면 되는 것이다. (= 느슨한 결합, Loose Coupling)
어떤 하나의 상태값을 변경하거나 사용하는 곳이 많아질수록 옵저버 패턴은 매우 유용하게 느껴졌다. 상태를 변경하는 쪽에서 일일이 다음에 일어나야 할 일들을 정의할 필요가 없고, 그냥 필요한 쪽에서 한 번씩만 ‘구독’해두면 상태가 변할 때마다 알아서 수행되기 때문이다.
// 옵저버 패턴이 없을 때
function handleClickLikeButton() { // '추천' 버튼을 눌렀을 때
likeCount += 1;
renderPostLikeCount(); // 현재 게시글의 총 추천 수 갱신
renderLikeUserList(); // 현재 게시글에 추천을 누른 사용자 목록 갱신
renderMyLikeList(); // 사용자가 추천을 누른 게시글 목록 갱신
}
function handleClickCancelButton() { // '취소' 버튼을 눌렀을 때
likeCount -= 1; // 역시 똑같은 작업들을 해줘야 함
renderPostLikeCount();
renderLikeUserList();
renderMyLikeList();
}
// 옵저버 패턴을 사용할 때
likeCount.subscribe(renderPostLikeCount);
likeCount.subscribe(renderLikeUserList);
likeCount.subscribe(renderMyLikeList); // 구독 먼저 해주고
function handleClickLikeButton() {
likeCount.increase();
}
function handleClickLikeButton() {
likeCount.decrease(); // 값만 바꿔주면 끝!
}
리액트를 비롯한 현대의 많은 프레임워크들이 옵저버 패턴을 기반으로 만들어졌다고 한다. 직접 코드로 구현하면서 그 원리를 이해할 수 있어서 좋았다.