디자인 패턴 1 - 옵저버 패턴 observer pattern

·2020년 8월 30일
0

Design Patterns

목록 보기
2/2

👉 세션 발표 자료
👉 실습 코드

옵저버 패턴은 GOF 패턴 중 '행위' 패턴에 속합니다. 어떤 행위인지는 이름만 봐도 짐작이 되시겠죠, 바로 관찰입니다.

어떤 문제를 해결하고 있을까?

제가 좋아하는 유튜브 채널, 노마드 코더입니다. 이 채널을 '구독' 하면, 채널에 새로운 영상이 올라올 때마다 저를 비롯한 구독자들에게 '알람' 이 가겠죠 -

만약 구독과 알람이 없었다면 어땠을까요? 저는 궁금할 때마다 노마드 코더에 새로운 영상이 올라왔는지 확인하기 위해 노마드 채널 페이지를 방문해야 했을겁니다. 무척 번거로운데다 실시간성이 보장되지도 않죠.

이런 문제를 해결하기 위해 유튜브에서 구독과 알람 기능을 지원하듯, 같은 문제를 같은 방식으로 해결하고 있는 것이 바로 옵저버 패턴입니다.

그렇다면 개발을 할 때, 어떤 상황에서 옵저버 패턴을 적용해볼 수 있을까요? 품절된 어떤 상품의 재입고 여부를 실시간으로 보여줘야하는 컴포턴트 A가 있다고 가정해봅시다. A 컴포넌트가 주기적으로 재고 데이터를 관리하는 B에 접근하여 재입고 여부를 확인할 수도 있겠지만 그럴 필요 없이, 물건이 입고되었을 때 B가 A에게 알려주도록 옵저버 패턴을 적용할 수 있습니다. 훨씬 효율적이겠죠.

옵저버 패턴을 구성하는 두 종류의 객체 : Subject, Observer

옵저버 패턴에는 두 종류의 객체가 등장합니다. Subject 와 Observer 입니다. 각 객체의 역할이 무엇인지는 금방 짐작하실 수 있을거에요, Subject 가 데이터를 관리하고 전달하는 객체라면 Observer 는 Subject 를 관찰하며 데이터의 변화를 전달받는 객체입니다. 그리고 두 객체는 일대다 관계를 맺죠. 유튜브 채널과 subject 구독자들 observers 의 관계가 일대다이듯이요.

옵저버 패턴에 등장하는 단어를 알게 되었으니 옵저버 패턴이 하고있는 '행위'를 다시 한 번 정리해볼게요. 옵저버 패턴에서는 데이터를 관리하는 Subject 객체가 데이터에 변곡점이 생겼을 때 자신을 구독하고 있는 Observer 들에게 변경된 데이터를 전달합니다.

어떻게 가능한걸까? (TypeScript 로 직접 구현해보자!)

저는 옵저버 패턴에 대한 글들을 읽으며 한 객체가 다른 객체를 구독하고 변화를 감지하는게 도대체 어떻게 가능한지 궁금했습니다. 구독하고 알람을 받는다는 개념 자체는 간단했지만 이걸 직접 구현할 자신은 없었습니다. 너무 어렵고 복잡할 것 같았거든요. 그러나 의외로! 간단합니다.

디자인 패턴 공부에 참고하고 있는 책 - 헤드퍼스트 디자인 패턴에 등장하는 예시를 그대로 가져와보도록 하겠습니다.

  • 날씨와 관련된 데이터인 기온, 습도, 기압을 관리하는 WeatherData 라는 객체가 있습니다.
  • 이 객체는 기상청으로부터 최신 데이터를 받아오고 있습니다.
  • 현재 날씨, 날씨 통계, 날씨 예보 - 3개 화면은 모두 이 객체로부터 새로운 데이터를 전달받는 상황이라고 가정해봅시다.

먼저 인터페이스를 구현해보도록 할까요.

export interface Subject {
    registerObserver(o: Observer): void 
    removeObserver(o: Observer): void 
    notifyObserver(): void 
}

export interface Observer {
    update(t: number, h: number, p: number): void 
}

Subject 인터페이스는 구독자를 등록하고, 구독을 해지하고, 구독자들에게 정보를 알려주는 메서드를 가집니다. Observer 인터페이스는 인자로 데이터를 전달받는 메서드를 가지고 있죠.

그럼 각 인터페이스를 구현하는 객체를 만들어보겠습니다.

class WeatherData implements Subject {

    observers: Array<Observer>;
    temperature: number;
    humidity: number;
    pressure: number;

    constructor() {
        this.observers = [];
        this.temperature = 0;
        this.humidity = 0;
        this.pressure = 0;
    }

    setMeasurements(
        temperature: number,
        humidity: number,
        pressure: number
    ) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure
        this.measurementsChanged()
    }

    measurementsChanged() {
        this.notifyObserver()
    }
    
    registerObserver(o: Observer) {
        this.observers.push(o);
    }

    removeObserver(o: Observer) {
        const targetIndex = this.observers.indexOf(o);
        if (targetIndex >= 0) {
            this.observers.splice(targetIndex, 1);
        }
    }
    
    notifyObserver () {
        for(const o of this.observers) {
            o.update(this.temperature, this.humidity, this.temperature);
        }
    }
}


class CurrentDisplay implements Observer {
    temperature: number = 0;
    humidity: number = 0;
    pressure: number = 0;

    update(t: number, h: number, p: number) {
        this.temperature = t;
        this.humidity = h;
        this.pressure = p;
    }

}

class StatisticsDisplay implements Observer {
    temperature: number = 0;
    humidity: number = 0;
    pressure: number = 0;

    update(t: number, h: number, p: number) {
        this.temperature = t;
        this.humidity = h;
        this.pressure = p;
    }

}

class ForecastDisplay implements  Observer {
    temperature: number = 0;
    humidity: number = 0;
    pressure: number = 0;

    update(t: number, h: number, p: number) {
        this.temperature = t;
        this.humidity = h;
        this.pressure = p;
    }
}


const main = () => {
    const weatherData: WeatherData = new WeatherData();

    const currentDisplay: CurrentDisplay = new CurrentDisplay();
    const statisticDisplay: StatisticsDisplay = new StatisticsDisplay();
    const forecastDisplay: ForecastDisplay = new ForecastDisplay();

    weatherData.registerObserver(currentDisplay);
    weatherData.registerObserver(statisticDisplay);
    weatherData.registerObserver(forecastDisplay);
};

Subject 객체가 구독자들을 관리하는 배열 observers 를 가지고 있고, observer 객체들이 구독을 하거나 구독을 해제할 때마다 배열에 해당 observer 를 추가하거나 (push) 제거하기만 하면 (splice) 얼마든지 구독자를 관리할 수 있습니다. 게다가 구독 상태인 observer 들이 interface 로 약속한대로 update 라는 메서드를 가지고만 있다면, 배열 내 모든 요소들 즉 모든 구독자들의 update 메서드를 실행시켜 한 번에 새로운 데이터를 전달할 수 있죠.

대표적인 라이브러리들

보셨듯 간단해서 직접 구현할 수도 있지만 이미 옵저버 패턴 쓰인 라이브러리들이 많이 있습니다. 자바스크립트에서는 대표적으로는 RxJS, MobX 가 있다고 합니다.

0개의 댓글