의도
- 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴
문제
- 휴대폰을 파는 가게가 있고 새로운 아이폰이 출시되었다 가정해보자.
- 손님이 새 아이폰을 사고싶다면 가게에 직접갈 수 있지만, 가게에서 재고가 없는 등의 문제가 발생할 수 있다.
- 반대로 재고가 입고됬을 때 가게가 알림을 보낼수도 있지만, 이는 원하지 않는 손님들에게 불편을 끼치게 된다.
해결책
- 알림을 보내는 클래스에 구독, 구독 취소 메커니즘을 구현하기
- 현재 구독중인 객체를 참조하는 배열과, 구독자를 추가, 제거하는 공개 메서드를 제작
- 알림을 보낼때에는 구독을 하고 있는 객체의 알림 메서드를 사용하되, 이를 공통된 인터페이스로 정리하는 것을 권장함
구조
interface Publisher {
attach(subscriber: Subscriber): void;
detach(subscriber: Subscriber): void;
notify(): void;
}
class ConcretePublisher implements Publisher {
public state: number;
private subscribers: Subscriber[] = [];
public attach(subscriber: Subscriber): void {
const isExist = this.subscribers.includes(subscriber);
if (isExist) {
return console.log('Subject: Subscriber has been attached already.');
}
console.log('Subject: Attached an subscriber.');
this.subscribers.push(subscriber);
}
public detach(subscriber: Subscriber): void {
const subscriberIndex = this.subscribers.indexOf(subscriber);
if (subscriberIndex === -1) {
return console.log('Subject: Nonexistent observer.');
}
this.subscribers.splice(observerIndex, 1);
console.log('Subject: Detached an observer.');
}
public notify(): void {
console.log('Subject: Notifying observers...');
for (const subscriber of this.subscribers) {
subscriber.update(this);
}
}
public someBusinessLogic(): void {
console.log('\nSubject: I\'m doing something important.');
this.state = Math.floor(Math.random() * (10 + 1));
console.log(`Subject: My state has just changed to: ${this.state}`);
this.notify();
}
}
interface Subscriber {
update(publisher: Publisher): void;
}
class ConcreteSubscriberA implements Subscriber {
public update(publisher: Publisher): void {
if (publisher instanceof ConcretePublisher && publisher.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.');
}
}
}
class ConcreteSubscriberB implements Subscriber {
public update(publisher: Publisher): void {
if (publisher instanceof ConcretePublisher && (publisher.state === 0 || publisher.state >= 2)) {
console.log('ConcreteObserverB: Reacted to the event.');
}
}
}
const publisher = new ConcretePublisher();
const subscriber1 = new ConcreteSubscriberA();
publisher.attach(subscriber1);
const subscriber2 = new ConcreteSubscriberB();
publisher.attach(subscriber2);
publisher.someBusinessLogic();
publisher.someBusinessLogic();
publisher.detach(subscriber2);
publisher.someBusinessLogic();
적용
- 한 객체의 상태 변경이 다른 객체에 영향을 줄 때 사용
옵저버 패턴을 사용해 모든 객체가 상태 변경의 알림을 받아 자신들의 로직을 수정할 수 있도록 함
- 앱의 일부 객체들이 제한된 시간 동안, 특정 조건에서 다른 객체들을 관찰해야 할 때 사용
구독 리스트를 동적으로 변경할 수 있음
구현방법
- 비즈니스 로직을 상태 변화를 알리는 부분화 상태 변화를 구독하는 두 부분으로 나누기
- 각각의 부분에 대한 인터페이스 선언
구독을 하는 부분에서는 update 메서드가 필요하고, 알림을 주는 인터페이스에서는 구독자 객체에는 구독자를 구독/구독 해제 하는 메서드를 필요로 함
- 실제 구독을 하는 객체들과 알림을 주는 메서드를 구현하며 알림을 줄 때 구독을 하는 객체에 데이터를 전달함
필요한 경우 구독자들이 알림이 올 시 직접 데이터를 가져가도록 구현할 수도 있음
- 클라이언트 코드에서 필요한 구독자를 생성하고 적절한 알림 객체와 등록해야 함
장단점
- 알림을 주는 객체의 변화없이 구독자 객체 변경이 가능해 개방, 폐쇄 원칙 준수
- 런타임 환경에서 객체 간 관계를 형성할 수 있음
- 구독자들이 규칙없이 알람을 받을 수 있음