[Behavioral Patterns] - Observer

Lee Jeong Min·2022년 1월 23일
0

디자인 패턴

목록 보기
19/23
post-thumbnail

의도

옵저버 패턴은 일부 객체가 다른 객체에 대한 상태 변화를 알릴 수 있는 behavioral design pattern이다.

이 패턴은 구독자 인터페이스를 구현하는 모든 객체에 대해 이러한 이벤트를 구독하거나 구독을 취소할 수 있는 방법을 제공한다.

→ 다른 명칭으로 Event-Subscriber, Listener 라고 한다.

문제

두 가지 유형의 객체가 있다고 가정해보자. 하나는 고객, 다른 하나는 상점이다. 고객은 자신이 사고싶은 특정 제품(아직 나오지 않은 신상품)을 사기 위해
상점에 물건이 들어와 있는지 매일 매장을 방문한다. 그러나 이 행위의 대부분은 제품이 상점에 들어오기 전까지는 무의미하다.

상점에 방문하기 vs 스팸메시지 보내기

반면에, 상점은 새로운 제품이 출시될 때마다 모든 고객에게 엄청난 양의 이메일을 보낼 수 있다. 이것은 일부 고객들이 매일 매장에 방문하는 것을 막도록 할 수 있지만, 신제품에 관심없는 다른 고객들에게는 매우 불편함이 될 것이다.

해결책

퍼블리셔라는 객체에게 상태 변화를 알리는 객체가 있고, 이 퍼블리셔의 상태에 대한 변경 사항을 추적하려는 다른 모든 객체를 구독자라고 한다.

옵저버 패턴은 개별 객체가 해당 게시자에서 오는 이벤트 스트림을 구독하거나 구독을 취소할 수 있도록 퍼블리셔 클래스에 구독 메커니즘을 추가하는 것을 제안한다. 이는 다음과 같이 구성된다.

1) 구독자 객체에 대한 참조 목록을 저장하기 위한 배열 필드
2) 그 목록에 구독자를 추가하고 목록에서 제거할 수 있는 여러 공개 메서드

구독 메커니즘을 통해 개별 객체가 이벤트 알림을 구독할 수 있다.

이제는 퍼블리셔에 중요한 이벤트가 발생할 때마다 구독자를 넘겨받아 특정 알림 방법을 객체에 호출한다.

실제 앱에는 동일한 퍼블리셔 클래스의 이벤트를 추적하는 데 관심이 있는 수십 개의 다른 구독자 클래스가 있을 수 있다. 퍼블리셔를 모든 이 클래스에 연결시키고 싶지 않을 것이고 만약 퍼블리셔 클래스가 다른 사람들에 의해 사용되어야 한다면 그들 중 일부에 대해 미리 알지 못할 수도 있다.

그렇기 때문에 구독자는 동일한 인터페이스를 구현하고 퍼블리셔는 해당 인터페이스를 통해서만 그들과 통신하도록 만들어야 한다. 이 인터페이스는 퍼블리셔가 알림과 함께 일부 컨텍스트 데이터를 전달하는 데 사용할 수 있는 매개 변수 집합과 함께 알림 방법을 선언해야 한다.

게시자는 구독자에게 객체에 대한 특정 알림 방법을 호출하여 알린다.

앱에 여러 유형의 퍼블리셔가 있고 구독자가 모든 퍼블리셔와 호환되도록 하려면 더 나아가 모든 퍼블리셔가 동일한 인터페이스를 따르도록 할 수 있다. 이 인터페이스를 통해 구독자는 특정 클래스에 결합하지 않고 퍼블리셔의 상태를 관찰할 수 있다.

현실 유사성

잡지 및 신문 구독

신문이나 잡지는 다음 호가 나오면 퍼블리셔가 게시 직후 또는 미리 새 발간된 호를 우편함으로 직접 보낸다.

퍼블리셔는 구독자 명단을 관리하고 그들이 관심 있는 잡지를 알고 있다. 또한 구독자들은 구독을 중지하고 싶을 때 언제든지 목록을 떠날 수 있다.

구조

  1. 퍼블리셔는 다른 객체에 대한 관심 이벤트를 발행한다. 이러한 이벤트는 게시자가 상태를 변경하거나 일부 동작을 실행할 때 발생한다. 퍼블리셔에 새 구독자가 가입하고 현재 구독자가 목록에서 나갈 수 있는 구독 인프라가 포함되어 있어야 한다.

  2. 새 이벤트가 발생하면 퍼블리셔는 구독 목록을 검토하고 각 구독자 객체의 인터페이스에 선언된 알림 방법을 호출한다.

  3. 구독자 인터페이스는 알림 인터페이스를 선언한다. 대부분의 경우 단일 업데이트 방법으로 구성된다. 메서드에는 퍼블리셔가 업데이트와 함께 일부 이벤트 세부 정보를 전달할 수 있는 여러 매개 변수가 있을 수 있다.

  4. 구체적 구독자는 퍼블리셔가 발행한 알림에 응답하여 몇 가지 작업을 수행한다.

  5. 일반적으로 구독자는 업데이트를 올바르게 처리하기 위해 일부 상황별 정보가 필요하다. 이러한 이유로 퍼블리셔는 종종 알림 방법의 인수로 일부 컨텍스트 데이터를 전달한다.

  6. 클라이언트는 퍼블리셔 객체와 구독자 객체를 별도로 만든 다음 퍼블리셔 업데이트를 위해 구독자를 등록한다.

적용가능성

  • 한 객체의 상태를 변경하면 다른 객체를 변경해야 할 수 있고 실제 객체 집합을 미리 알 수 없거나 동적으로 변경할 수 있는 경우 관찰자 패턴을 사용한다.

  • 앱의 일부 객체가 제한된 시간 동안 또는 특정 경우에만 다른 객체를 관찰해야 하는 경우 패턴을 사용하라.

장단점

장점

  • OCP

  • 런타임에 객체 간의 관계를 설정할 수 있다.

단점

  • 구독자는 임의 순서대로 통지된다.

Observer in TypeScript

TypeScript의 패턴 사용

복잡도: ★★☆

인기: ★★★

사용 예: 옵저버 패턴은 GUI 구성 요소에서 매우 흔하다. 클래스와 결합하지 않고 다른 객체에서 발생하는 이벤트에 반응하는 방법을 제공한다.

식별: 이 패턴은 목록에 객체를 저장하는 구독 메서드 및 해당 목록의 객체에 발급된 업데이트 메서드에 의해 인식될 수 있다.

index.ts

// 구독자를 관리하기 위한 메서드의 집합인 Subject 인터페이스를 선언한다.
interface Subject {
  // Subject에 옵저버를 부착한다.
  attach(observer: Observer): void;

  detach(obserevr: Observer): void;

  notify(): void;
}

// Subject는 상태 변화가 일어날 때 옵저버에게 알리는 기능과 중요한 상태를 소유하는 기능을 한다.
class ConcreteSubject implements Subject {
  public state: number;

  private observers: Observer[] = [];

  public attach(observer: Observer): void {
    const isExist = this.observers.includes(observer);
    if (isExist) {
      return console.log('Subject: Observer has been attached already.');
    }

    console.log('Subject: Attached an observer.');
    this.observers.push(observer);
  }

  public detach(observer: Observer): void {
    const observerIndex = this.observers.indexOf(observer);
    if (observerIndex === -1) {
      return console.log('Subject: Nonexistent observer.');
    }

    this.observers.splice(observerIndex, 1);
    console.log('Subject: Detached an observer.');
  }

  public notify(): void {
    console.log('Subject: Notifying observers...');
    for (const observer of this.observers) {
      observer.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 Observer {
  update(subject: Subject): void;
}

class ConcreteObserverA implements Observer {
  public update(subject: Subject): void {
    if (subject instanceof ConcreteSubject && subject.state < 3) {
      console.log('ConcreteObserverA: Reacted to the event.');
    }
  }
}

class ConcreteObserverB implements Observer {
  public update(subject: Subject): void {
    if (subject instanceof ConcreteSubject && (subject.state === 0 || subject.state >= 2)) {
      console.log('ConcreteObserverB: Reacted to the event.');
    }
  }
}

const subject = new ConcreteSubject();

const observer1 = new ConcreteObserverA();
subject.attach(observer1);

const observer2 = new ConcreteObserverB();
subject.attach(observer2);

subject.someBusinessLogic();
subject.someBusinessLogic();

subject.detach(observer2);

subject.someBusinessLogic();

결과

요약

옵저버 패턴은 일부 객체가 다른 객체에 대한 상태 변화를 알릴 수 있게 이벤트를 구독하거나 구독을 취소할 수 있는 메서드를 제공한다.

리덕스 라이브러리 또한 옵저버 패턴을 사용하여 dispatch를 호출하면 dispatch 안에 구현된 reducer를 통해 액션에 따른 상태가 변경되어 리스너를 호출함 또한 안에 구독을 해지하는 클린업 함수또한 구현되어 있음

참고 사이트

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글