flagtags / OOP-study

0 stars 0 forks source link

2장 - 옵저버 패턴 #11

Open j03y14 opened 5 months ago

j03y14 commented 5 months ago

기상 스테이션 구축 프로젝트

WeatherData 객체를 바탕으로 구축

WeatherData 객체는 다음 세가지 객체를 추적

화면에 표시할 항목

추가 요구사항

class Display {
    update(temerature: number, humidity: number, pressure: number) {

    }
}

// 일단 임시로 같은거로..
const currentConditionDisplay = new Display();
const statisticsDisplay = new Display();
const forecastDisplay = new Display();

class WeatherData {
    getTemperature() {
        return 0;
    }

    getHumidity() {
        return 0
    }

    getPressure() {
        return 0
    }

    // 기상 관측값이 갱신될 때마다 호출되는 함수
    measurementsChanged() { 
        const temperature = this.getTemperature();
        const humidity = this.getHumidity();
        const pressure = this.getPressure();

        currentConditionDisplay.update(temperature, humidity, pressure);
        statisticsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(temperature, humidity, pressure);
    }
}

문제점

옵저버 패턴

신문사 + 구독자 => 옵저버 패턴

subject를 구독해놓고, subject의 데이터가 변경되면 옵저버에게 전달이 된다.

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 일대다 의존성을 정의한다.

느슨한 결합

서로 상호작용할 수 있지만 서로 잘 모르는 관계를 의미.

❗ 디자인원칙 4 - 가능하면 느슨한 결합을 사용해라.

interface Observer {
    // 측정치를 직접 전달하고 있음.
    // 이 부분이 변경될 가능성이 있는데 캡슐화가 안 되어있음.
    update(temperature: number, humidity: number, pressure: number): void
}

interface Subject {
    registerObserver(observer: Observer): void;
    removeObserver(observer: Object): void;
    notifyObservers(): void;
}

interface DisplayElement {
    display()
}

class CurrentConditionsDisplay implements Observer, DisplayElement {

    private temperature: number;
    private humidity: number;
    private pressure: number;

    private weatherData: WeatherData // 저장을 하는 이유는 나중에 옵저버에서 탈퇴할 때 써먹으려고.

    constructor(weathreData: WeatherData) {
        this.weatherData = weathreData;

        this.weatherData.registerObserver(this)
    }

    update(temperature: number, humidity: number, pressure: number) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;

        this.display()
    }

    display() {
        console.log(`현재상태: 온도 ${this.temperature}, 습도 ${this.humidity}, 기압 ${this.pressure}`)
    }
}

class WeatherData implements Subject {
    private observers: Observer[];

    private temperature: number;
    private humidity: number;
    private pressure: number;

    getTemperature() {
        return this.temperature;
    }

    getHumidity() {
        return this.humidity
    }

    getPressure() {
        return this.pressure
    }

    registerObserver(observer: Observer): void {
        this.observers.push(observer)
    }

    removeObserver(observer: Object): void {
        this.observers = this.observers.filter(o => o === observer);
    }

    notifyObservers(): void {
        this.observers.forEach((o) => {
            o.update(this.temperature, this.humidity, this.pressure)
        })
    }

    // 기상 관측값이 갱신될 때마다 호출되는 함수
    measurementsChanged() { 
        this.notifyObservers();
    }

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

const weatherData = new WeatherData();
const currentDisplay = new CurrentConditionsDisplay(weatherData);

subject에 getter를 만들어서 옵저버에서 호출하게 하면 안되나? 필요할 때마다 여러번 호출을 해야함. 옵저버 패턴의 단점은 각 옵저버가 필요한 상태만 받는 것이 아니라 전부 받아야 하는 것

푸시 방식 - 옵저버 패턴

풀 방식 - getter를 만드는 방식

대체로는 필요한 데이터만 골라서 가져가도록 하는 방법이 좋다.

interface Observer {
    update(): void
}

interface Subject {
    registerObserver(observer: Observer): void;
    removeObserver(observer: Object): void;
    notifyObservers(): void;
}

interface DisplayElement {
    display()
}

class CurrentConditionsDisplay implements Observer, DisplayElement {

    private temperature: number;
    private humidity: number;
    private pressure: number;

    private weatherData: WeatherData // 저장을 하는 이유는 나중에 옵저버에서 탈퇴할 때 써먹으려고.

    constructor(weathreData: WeatherData) {
        this.weatherData = weathreData;

        this.weatherData.registerObserver(this)
    }

    update() {
        // 가져가 라고 하면 가져가는 방식
        this.temperature = this.weatherData.getTemperature();
        this.humidity = this.weatherData.getHumidity();

        this.display()
    }

    display() {
        console.log(`현재상태: 온도 ${this.temperature}, 습도 ${this.humidity}, 기압 ${this.pressure}`)
    }
}

class WeatherData implements Subject {
    private observers: Observer[];

    private temperature: number;
    private humidity: number;
    private pressure: number;

    getTemperature() {
        return this.temperature;
    }

    getHumidity() {
        return this.humidity
    }

    getPressure() {
        return this.pressure
    }

    registerObserver(observer: Observer): void {
        this.observers.push(observer)
    }

    removeObserver(observer: Object): void {
        this.observers = this.observers.filter(o => o === observer);
    }

    notifyObservers(): void {
        this.observers.forEach((o) => {
            o.update()
        })
    }

    // 기상 관측값이 갱신될 때마다 호출되는 함수
    measurementsChanged() { 
        this.notifyObservers();
    }

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

const weatherData = new WeatherData();
const currentDisplay = new CurrentConditionsDisplay(weatherData);

weatherData.setMeasureMents(1, 2, 3)

옵저버 패턴의 예시

버튼, 이벤트 리스너

그래서 옵저버 패턴을 언제 쓰나?

kkirico commented 5 months ago

2장. 옵저버패턴

기상 데이터 예시

public class WeatherData {
    public void measurementsChanged() {
        float temp = getTemparature();
        float humidity = getHumidity();
        float pressure = getPressure();
    }

    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);
}

문제점

옵저버 패턴을 사용해보자

옵저버 패턴이란 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.

느슨한 결합의 위력

: 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계

주제(subject)는 옵저버가 특정 인터페이스를 구현한다는 사실만을 압니다.

옵저버는 언제든지 추가/제거 할 수 있습니다.

새로운 형식의 옵저버를 추가해도 주제를 변경할 필요가 없습니다.

주제와 옵저버는 독립적으로 재사용 가능합니다.

주제가 옵저버와 달라져도 서로 영향을 끼치지 않습니다.

class WeatherData implements Subject {
    private observers;
    private temparature;
    private humidity;
    private pressure;

    function registerObserver(o: Observer) {
        observers.add(o);
    }

    function removeObserver(){}

    function notifyObserver(){}

    function setMeasurements(temparature, humidity, pressure){

        this.temparature = temparature
        // ...
        measurementChanged(); // just for analytics?
    }

    class CurrentConditionDisplay implements Observer, DisplayElement {
        this.weatherData

    }

푸시를 풀로 바꾸기

주제가 옵저버로 데이터를 보내는 푸시 방식보다, 옵저버가 주제로 부터 데이터를 당겨오는 풀 방식의 선택은 구현의 차이입니다.

애플리케이션의 변화에 적응하기 쉬운 것은 풀 방식이빈다. 주제가 자신의 데이터에 관한 게터 메소드를 지원하고, notify가 발생할 때 마다 게터 메소드를 호출하빈다.

언제 옵저버 패턴을 쓸까?

  1. 일대 다 관계, 일방향
  2. subject의 변경 시 옵저버에게 알려져야함
    • 옵저버의 추가나 제거가 예측된다면