디자인 패턴은 SW 개발을 하면서 발생하는 이슈들을 해결하는데 도움을 주는 기술들이다. 오늘은 JS의 대표적인 디자인패턴 중 Observer 패턴에 대해서 알아보도록 하자.
객체의 상태 변화를 관찰하는 관찰자들(즉 Observers)의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 목록에 있는 Observer에게 통지하도록 하는 디자인 패턴이다.
어떤 객체(View)가 변화할 때 이와 연관된 여러 객체에게 직접 통보하지 않고 중간관리자를 두어 변화의 감지와 통보를 대신하게 만드는 것이다.
예를 들어, To-do list를 생각해보자.
할 일이 하나 추가되면 total count도 그에 맞게 변화한다.
1. 할 일이 추가되는 이벤트 발생
2. 성공시, total count가 +1 / 삭제시, total count가 -1
위에서 예로 들었던 To-do list를 다시 생각해보자.
지금까지는 단순하게 할 일이 추가되는 뷰와 total count 뷰 이렇게 두 가지 뷰끼리만 소통했지만, 만약 이 할 일 목록이 변화할 때마다 함께 유기적으로 변해야 하는 ui 요소가 5개, 10개...가 된다면 추가할 때마다 그 모든 뷰를 렌더링 하는 함수를 하나하나 불러와서 실행시켜주어야 한다.
이때부터 무언가 격하게 잘못되었다는 느낌이 들죠...ㄱ-
이 때 Observer Pattern이 등장한다.
1. 먼저 할 일 목록의 변화를 대신 감지하고 통보하는 중간관리자(publisher/발행하는 객체)를 만든다.
2. 그리고 할 일 목록과 관련이 있는 모든 뷰는 'Observer' 또는 'Listner'라고 부른다.
3. 이 중간관리자가 할일 목록의 변화를 감지하면 Observer들의 렌더링 함수를 대신 실행시킨다.
4. 이러면 할 일 목록 뷰에서 직접 다른 뷰로 이벤트를 하나하나 보낼 필요 없이, 중간관리자에게만 자신의 변화를 알리면 된다. (물론 Observer에게 최초 한 번은 어떤 변화/이벤트를 감지해야 하고, 이때 어떤 함수를 실행해야 할지 알리는 작업은 필요하다. - subscribe)
이렇게 중간관리자가 대신 처리하게 하면 뷰끼리의 의존성이 없어지기 때문에 복잡한 구성에서도 각각의 뷰가 처리해야 되는 부분만 고민하면 된다.
즉, 객체끼리의 의존성이 낮아져 결합도가 낮아지기 때문에 유지보수에도, 확장성 면에서도 유리하다.
React나 Vue에서도 상태의 변화를 감지하기 위해 하나하나 props로 내려주는 대신 상태관리를 통해 변화를 알리고 그 변화를 감지하여 다시 렌더링 되는 것과 비슷한 느낌인 것이다.
// 관찰할 대상
export default class Subject {
constructor() {
this.observers = [] // 객체의 변화를 감지할 observer 리스트
}
registerObserver(observer) {
this.observers.push(observer) // 감시할 observer 등록
}
unregisterObserver(observer) {
this.observers = this.observers.filter(registedObserver => registedObserver !== observer) // 등록된 observer 해제
}
notifyObservers(data) {
// 객체 형태로 넣어주기
this.observers.forEach(observer => observer.notify(data)) // observer 로직 실행
}
}
import Subject from './subject.js'
const subject = new Subject(document.body)
const observer1 = { notify: data => console.log('observer1: ', data) }
const observer2 = { notify: data => console.log('observer2: ', data) }
// subject(관찰 대상)에 변경이 있을 때마다 연결된 모든 observer에 알려주게 된다. (이것이 Observer Pattern)
subject.registerObserver(observer1)
subject.unregisterObserver(observer2)
subject.notifyObservers('notifyObservers')
window.subject = subject
보시면 알겠지만, Subject라는 친구가 Observer들을 가지는 배열(this.observers)과 Observer를 등록하는 함수(registerObserver) 그리고 제거하는 함수(unregisterObserver)로 이루어져 있음을 알 수 있다.
여기서 Observer들은 함수를 넣었고 Observer가 하는 일은 Subject가 전달받은 데이터를 출력해주는 역할을 한다.
정리하자면, Subject는 Observer를 가지며, Observer에게 정보를 전달한다.
Observer는 Subject로부터 전달받은 데이터를 처리한다.