Observer[Design Pattern]

SnowCat·2023년 3월 27일
0

Design Pattern

목록 보기
19/23
post-thumbnail

의도

  • 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴

문제

  • 휴대폰을 파는 가게가 있고 새로운 아이폰이 출시되었다 가정해보자.
  • 손님이 새 아이폰을 사고싶다면 가게에 직접갈 수 있지만, 가게에서 재고가 없는 등의 문제가 발생할 수 있다.
  • 반대로 재고가 입고됬을 때 가게가 알림을 보낼수도 있지만, 이는 원하지 않는 손님들에게 불편을 끼치게 된다.

해결책

  • 알림을 보내는 클래스에 구독, 구독 취소 메커니즘을 구현하기
  • 현재 구독중인 객체를 참조하는 배열과, 구독자를 추가, 제거하는 공개 메서드를 제작
  • 알림을 보낼때에는 구독을 하고 있는 객체의 알림 메서드를 사용하되, 이를 공통된 인터페이스로 정리하는 것을 권장함

구조

/**
 * Publisher 인터페이스로 객체를 구독하고 구독 취소하고 알림을 전달하는 메서드가 존재함
 */
interface Publisher {
    // Attach an observer to the subject.
    attach(subscriber: Subscriber): void;

    // Detach an observer from the subject.
    detach(subscriber: Subscriber): void;

    // Notify all observers about an event.
    notify(): void;
}

/**
 * 실제 publisher 구현시에는 상태 변화를 감지해 변화가 있을 시 구독을 한 객체에 알림을 전달하는 로직을 작성하게 됨
 */
class ConcretePublisher implements Publisher {
    // state를 저장하는 변수
    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); //Subject: Attached an observer.

const subscriber2 = new ConcreteSubscriberB();
publisher.attach(subscriber2); //Subject: Attached an observer.

publisher.someBusinessLogic();
/*
Subject: I'm doing something important.
Subject: My state has just changed to: 6
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event.
*/

publisher.someBusinessLogic();
/*
Subject: I'm doing something important.
Subject: My state has just changed to: 1
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
*/

publisher.detach(subscriber2); // Subject: Detached an observer.

publisher.someBusinessLogic();
/*
Subject: I'm doing something important.
Subject: My state has just changed to: 5
Subject: Notifying observers...
*/

적용

  • 한 객체의 상태 변경이 다른 객체에 영향을 줄 때 사용
    옵저버 패턴을 사용해 모든 객체가 상태 변경의 알림을 받아 자신들의 로직을 수정할 수 있도록 함
  • 앱의 일부 객체들이 제한된 시간 동안, 특정 조건에서 다른 객체들을 관찰해야 할 때 사용
    구독 리스트를 동적으로 변경할 수 있음

구현방법

  1. 비즈니스 로직을 상태 변화를 알리는 부분화 상태 변화를 구독하는 두 부분으로 나누기
  2. 각각의 부분에 대한 인터페이스 선언
    구독을 하는 부분에서는 update 메서드가 필요하고, 알림을 주는 인터페이스에서는 구독자 객체에는 구독자를 구독/구독 해제 하는 메서드를 필요로 함
  3. 실제 구독을 하는 객체들과 알림을 주는 메서드를 구현하며 알림을 줄 때 구독을 하는 객체에 데이터를 전달함
    필요한 경우 구독자들이 알림이 올 시 직접 데이터를 가져가도록 구현할 수도 있음
  4. 클라이언트 코드에서 필요한 구독자를 생성하고 적절한 알림 객체와 등록해야 함

장단점

  • 알림을 주는 객체의 변화없이 구독자 객체 변경이 가능해 개방, 폐쇄 원칙 준수
  • 런타임 환경에서 객체 간 관계를 형성할 수 있음
  • 구독자들이 규칙없이 알람을 받을 수 있음
profile
냐아아아아아아아아앙

0개의 댓글