Earn this, Earn it.
디자인 패턴에 대해서 - 2) 옵저버(Observer) 패턴 본문
옵저버(Observer) 패턴이란?
객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴입니다.
주로 분산 이벤트 핸들링 시스템을 구현하는 데에 사용되며 발행/구독 모델(Pub-Sub)로 알려져 있기도 합니다.
이 패턴의 핵심은 옵저버 또는 리스너라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록시키고 각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리하는 것입니다.
어떤 문제들을 해결하기 위함일까?
정보의 단위가 커질수록, 객체들의 규모가 커질수록 프로그램 복잡성이 증가하게 된다. 이때 가이드라인을 제시해주는 패턴이라고 간단히 얘기할 수 있겠습니다. 그렇다면 어떻게 옵저버 패턴이 이런 문제들을 해결할 수 있을까요?
여기서 크게 관찰 대상 객체(Observer)와 주체 객체(Subject)가 등장합니다. 이러한 객체들은 우리 실생활에서도 흔히 볼 수 있는데요, 신문을 발행하는 신문사와 그 신문을 구독하는 구독자의 관계라든지 위의 그림에 나와있는 유튜브 채널과 구독자의 관계 등에서 찾아볼 수 있습니다. 어떤 정보를 얻고자 하는 주체(Subject)는 Publisher와 관계를 맺고 정보를 구독합니다. 이렇게 지속되다가 더 이상 정보가 필요없어지는 경우 이를 Publisher에게 알리는 방법으로 간단히 구독을 해지할 수 있죠.
따라서 프로그램이 복잡해질 때, 한 객체에서의 상태가 변화할 때마다 그 객체와 의존 관계에 있는 모든 객체들의 상태를 갱신하면서도 객체 간 결합도를 줄이기 위한 방법으로 옵저버 패턴을 사용하게 되는 것입니다.
또한, 상태에 따라 View가 바뀌는 UI에서 상태변화를 감지하기 위한 방법으로도 사용하게 됩니다.
어떤 상황에 적용할 수 있을까?
JavaScript의 addEventListner 함수를 예로 들어보겠습니다. A라는 버튼에 '클릭'이라는 이벤트를 구독하고 있는 a,b,c라는 객체가 등록되어 있다고 생각합시다. 그러면 버튼이라는 객체가 Publisher가 되고 이 addEventListner가 Observer가 되어 '클릭'라는 이벤트가 발생하면 이를 구독하고 있는 a,b,c 주체(Subject)에 알려주게 되는 것입니다. 이와 같은 패턴을 대표적으로 옵저버 패턴이라고 합니다. 이는 addEventListner뿐 아니라 커스텀 이벤트나 이벤트 에미터(Event Emitter)를 통해서도 같은 방식으로 구현할 수 있습니다.
즉, 웹이나 모바일에서 UI와 관련된 곳에서 옵저버 패턴이 많이 쓰일 수 있습니다.
발행(Pub)/구독(Sub) 패턴과의 차이?
옵저버 패턴은 크게 주체(Subject)와 옵저버(Observer)로 이루어져 있습니다. 위에서 살펴본 예시에서 Publisher가 추가된 Pub-Sub 패턴과의 차이는 무엇일까요?
이 둘은 개념적으로 상당히 유사한 패턴입니다. 쉽게 얘기하자면 구독-발행 패턴은 옵저버 패턴의 변화된 형태라고 볼 수 있으므로 정확하게 둘을 구분해야 할 필요는 없습니다. 그래도 둘의 차이점을 살펴보면 다음과 같습니다.
구성 | 옵저버 패턴 | 구독-발행 패턴 |
의존성 | 옵저버들이 객체들을 알고 있으며, 객체들도 옵저버들을 알고 있다. | Publisher와 subscriber는 서로를 몰라도 되고, 메세지 큐(또는 브로커)를 통해 소통한다. |
상대적 결합성 | 높음 | 낮음 |
구현방식 | 대부분 동기 방식 (이벤트 발생시 객체가 옵저버의 메서드를 호출) |
비동기 방식 (메세지 큐를 사용) |
확장성 | 하나의 애플리케이션에서 구현 | 크로스 애플리케이션 구현 |
예제 코드
// Subject
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers.filter(obs=> observer!==obs );
}
notifyAll(changeOfState) {
this.observers.forEach((observer) => observer.notify(changeOfState));
}
}
// App.js
const observer1 = { notify: (change) => console.log(`${change} 버튼이 클릭됨.`) };
const observer2 = { notify: (change) => console.log(`${change} 숫자가 증가됨.`) };
// 주체
const subject = new Subject();
// 옵저버들을 구독
subject.subscribe(observer1);
subject.subscribe(observer2);
// 상태변화가 생기면 모든 관찰자에게 알리고, 각 관찰자는 통지를 받은 즉시 후속작업을 처리한다.
subject.observer.notifyAll('Like');
// observer1 : Like 버튼이 클릭됨.
// observer2 : Like 숫자가 증가됨.
// 구독 해지
subject.observer.unsubscribe(observer2);
subject.observer.notifyAll('Like');
// observer1 : Like 버튼이 클릭됨.
장단점
장점
1. 실시간으로 한 객체의 변경사항을 다른 객체에 전파 가능합니다.
2. 느슨한 결합으로 시스템이 유연하고 객체간의 의존성을 제거할 수 있습니다.
3. pull 방식이 아닌 push 방식을 사용함으로써 직관적으로 이해하기 쉽습니다.
단점
1. 너무 많이 사용하게 되면, 자칫 상태 관리가 복잡해질 수 있습니다.
2. 데이터 배분에 문제가 생기면 자칫 큰 문제로 이어질 수 있습니다.
3. (비동기로 구현된 경우)구독을 신청/취소하는 동안 원하는 결괏값을 얻기 힘들수도 있습니다.
4. observer 를 제때 제거해주지 않으면 메모리 누수가 일어날 수 있습니다.
'[개발 공부]' 카테고리의 다른 글
TIL - 식별관계와 비식별관계 (0) | 2021.09.28 |
---|---|
TIL - 내멋대로 정리하는 this, bind, apply, call (0) | 2021.09.24 |
디자인 패턴에 대해서 - 1) 디자인 패턴의 개요 (0) | 2021.09.23 |
파워토이(PowerToys)로 개발 능률 올리자! (0) | 2021.09.23 |
JavaScript로 직접 SPA 구현하면서 느낀 React (0) | 2021.09.22 |