옵저버 패턴은 옵저버(=관찰자)들이 관찰하고 있는 대상자의 상태가 변화가 있을 때마다 대상자는 직접 혹은 간접적으로 각 관찰자들에게 변화를 통지하고, 관찰자들은 알림을 받아 조치를 취하는 행동 패턴 ⇒ Publisher/Subscriber(발행/구독) 모델
- 1:M 의존성을 갖는다.
- 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용된다.
ex) 유튜브 구독
- 유튜버 = 발행자 = 대상자
- 구독자 = 관찰자
유튜버가 영상을 올리거나 글이 올라오면 구독자들에게 영상이 업로드 되거나 글이 올라왔다는 알림이 온다. 유튜브 채널을 구독하지 않은 사람들에게는 알림이 가지 않는다.
옵저버 패턴 흐름
- Subject(관찰 대상자)와 여러개의 Observer(관찰자)가 일 대 다 관계로 구성되어 있음
- 관찰 대상인 Subject 상태가 변경되면 Observer에게 변경 사항을 통보한다. ⇒ notifyObserver()
- 통보를 받은 Observer는 값을 변경하거나 삭제하거나 등 동작을 수행한다 ⇒ update()
- Observer들은 Observer 리스트에서 추가 혹은 삭제될 수 있다.
// 관찰 대상자 = 발행자
interface ISubject {
registerObserver(o: IObserver): void;
removeObserver(o: IObserver): void;
notifyObserver(): void;
}
class Subject implements ISubject {
// 관찰자들을 등록해서 담는 리스트
observers: IObserver[] = [];
// 관찰자 리스트에 등록
registerObserver(o: IObserver) {
this.observers.push(o);
console.log(o + ' 구독 완료');
}
// 관찰자 리스트에서 삭제
removeObserver(o: IObserver): void {
this.observers = this.observers.filter((ob) => ob !== o);
console.log(o + ' 구독 취소');
}
// 관찰자에게 이벤트 송신
notifyObserver(): void {
for (let observer of this.observers) {
observer.update();
}
}
}
// 관찰자 = 구독자
interface IObserver {
update(): void;
}
class ObserverA implements IObserver {
update() {
console.log('ObserverA님! 알림이 도착하였습니다.');
}
toString() {
return 'ObserverA';
}
}
class ObserverB implements IObserver {
update() {
console.log('ObserverB님! 알림이 도착하였습니다.');
}
toString() {
return 'ObserverB';
}
}
// 발행자 등록
const publisher = new Subject();
// 구독자 리스트로 등록
const o1 = new ObserverA();
const o2 = new ObserverB();
publisher.registerObserver(o1); // ObserverA 구독 완료
publisher.registerObserver(o2); // ObserverB 구독 완료
// 이벤트 전파
publisher.notifyObserver();
// ObserverA만 구독 취소
publisher.removeObserver(o1); // ObserverA 구독 취소
// ObserverB에게만 이벤트 전파
publisher.notifyObserver(); // ObserverB님! 알림이 도착하였습니다.
사용 상황
- 대상 객체의 상태가 변경될 때마다 다른 객체의 동작을 트리거해야 할 때
- 한 객체의 상태가 변경되면 다른 객체도 변경해야 할 때
장점
- Observer가 Subject의 상태 변화를 주기적으로 조회하지 않아도 자동으로 감지 가능
- Subject 코드 변경하지 않고도 새로운 Observer 추가 가능 ⇒ OCP 준수
- 런타임 시점에서 발행자와 구독 알림 관계 맺을 수 있음
- 상태 변경하는 객체와 변경 감지하는 객체 간 관계를 느슨하게 유지 가능
- Subject는 Observer가 누구인지 알 필요 없음
단점
- Observer는 알림 순서 제어할 수 없고 무작위 순서로 알림을 받는다.
- 코드 복잡도 중가
- 다수의 Observer 객체를 등록한 이후 해지하지 않으면 메모리 누수 발생
유튜브 구독 예제
// 유튜버
interface ISubject {
registerObserver(o: IObserver): void;
removeObserver(o: IObserver): void;
notifyObserver(): void;
}
class YoutuberSubject implements ISubject {
private _youtubeName: string;
constructor(name: string) {
this._youtubeName = name;
}
// 구독자들 관리하는 리스트
observers: IObserver[] = [];
// 동영상 리스트
videoList: string[] = [];
// 동영상 업로드
uploadVideo(link: string) {
this.videoList.push(link);
this.notifyObserver();
}
// 구독자 추가
registerObserver(o: IObserver) {
this.observers.push(o);
console.log(o + ' 구독 완료');
}
// 구독자 취소
removeObserver(o: IObserver): void {
this.observers = this.observers.filter((ob) => ob !== o);
console.log(o + ' 구독 취소');
}
// 구독자 알림 전송(업로드 시)
notifyObserver(): void {
for (let observer of this.observers) {
observer.alarm(this._youtubeName, observer._username);
}
}
}
// 구독자
interface IObserver {
_username: string;
alarm(youtubeName: string, name: string): void;
}
class Subscriber implements IObserver {
_username: string;
constructor(name: string) {
this._username = name;
}
alarm(youtubeName: string, name: string): void {
console.log(`${name}님! ${youtubeName}의 영상이 업로드되었습니다.`);
}
toString() {
return this._username;
}
}
// 유튜브 채널 등록
const publisher = new YoutuberSubject('observerPattern');
// 구독자 등록
const s1 = new Subscriber('SubscriberA');
const s2 = new Subscriber('SubscriberB');
const s3 = new Subscriber('SubscriberC');
publisher.registerObserver(s1);
publisher.registerObserver(s2);
publisher.registerObserver(s3);
// 영상 업로드
publisher.uploadVideo('first_video');
// SubscriberA 구독 취소
publisher.removeObserver(s1);
// 영상 업로드 -> A 제외하고 알림 감
publisher.uploadVideo('second_video');
[참고 사이트]
'면접을 위한 CS 전공지식 노트 > 1장 디자인 패턴과 프로그래밍 패러다임' 카테고리의 다른 글
MVP 패턴 (0) | 2024.09.10 |
---|---|
MVC 패턴 (1) | 2024.09.06 |
MVVM 패턴 (0) | 2024.09.05 |
전략 패턴 (0) | 2024.09.05 |