[Design Pattern] Observer Pattern이란?

Junseong·2021년 4월 26일
0
post-thumbnail

목차

  1. 등장 배경
  2. 패턴 설명
  3. 패턴 정의

본 시리즈는 'Head First Design Patterns' 책을 통해 공부한 내용을 정리하기 위해 작성되어졌습니다. 전체 코드는 Github 에서 확인할 수 있습니다.


등장 배경


옵저버 패턴은 '한 객체가 다른 객체의 정보를 계속 필요로 하는 의존 관계'를 구현할 때 JDK에서 많이 사용되는 패턴 가운데 하나입니다.

이번에는 기상 모니터링 애플리케이션을 통해 옵저버 패턴이 어떠한 이유로 등장했고 어떻게 사용하는지 알아봅시다.

기상 모니터링 애플리케이션은 기상 스테이션(실제 기상 정보를 수집하는 장비)으로 부터 습도, 온도, 기압 정보를 가져와 사용자들에게 실시간으로 기상 통계, 기상 예보, 기상 기록 화면을 제공하는 애플리케이션 입니다.

이 애플리케이션은 다음과 같이 설계되어 있습니다.

WeatherData 객체는 기상 스테이션으로 부터 각종 측정값들을 가져와 각 화면 객체에 전달합니다.

화면 객체는 WeatherData 객체로 부터 받은 측정값을 이용해 서비스 화면을 제공합니다.

설계서를 토대로 작성된 코드입니다.

// WeatherData 객체
public class WeatherData {
	private StatisticsDisplay statisticsDisplay;
	private ForecastDisplay forecastDisplay;
	private RecordDisplay recordDisplay;

	// 측정값들을 가져와 화면 객체에 전달
	public void measurementsChanged() {
		float temp = getTemperature();
		float humidity = getHumidity();
		float pressure = getPressure();

		statisticsDisplay.update(temp, humidity, pressure);
		forecastDisplay.update(temp, humidity, pressure);
		recordDisplay.update(temp, humidity, pressure);
	}
	
	// 기타 메서드
}
// 통계 화면 객체
public class StatisticsDisplay {
	private float temp;
	private float humidity;
	private float pressure;

	// 측정값을 받아와 화면에 출력
	public void update(temp, humidity, pressure) {
		this.temp = temp;
		this.humidity = humidity;
		this.pressure = pressure;

		display();
	}

	public void display() {
		// 기상 통계 정보를 화면에 출력
	}

}

이제 애플리케이션이 실시간으로 WeatherData 객체의 measurementsChanged()를 호출하기만 하면 사용자에게 실시간으로 기상 통계, 기상 예보, 기상 기록 서비스 화면을 제공할 수 있게 됩니다.

그런데 만약에 이 애플리케이션에 새로운 화면을 추가해야 된다면 WeatherData 객체의 코드를 수정하지 않고 추가할 수 있을까요?

또, 사용자가 직접 원하는 화면을 추가할 수 있게 해달라는 요구사항이 생기면 기존의 설계를 유지할 수 있을까요?

WeatherData 객체의 코드를 보면 WeatherData 객체가 화면 객체들을 각각 생성하여 가지고 있음을 볼 수 있습니다.

그 결과 두 객체 사이의 상호의존성이 매우 커지게 되었으며, 이처럼 객체간의 결합이 느슨하지 않은 프로그램은 변경 사항에 취약할 수 밖에 없습니다.

Observer Pattern은 서로 상호작용을 하는 객체들의 결합도를 느슨하게 만들어 변경 사항에 유연하게 대처할 수 있도록 하기 위해 등장하였습니다.


패턴 설명


옵저버 패턴 설명에 앞서 이 패턴과 유사한 신문 구독 메커니즘을 먼저 살펴봅시다.

신문 구독 서비스에서 구독자들은 신문사에 구독을 요청하여 신문을 받습니다. 신문사들은 구독 요청을 받고 구독자들에게 신문을 전달합니다.

신문사에는 구독자들에 관한 목록이 있습니다. 새로운 구독자가 생기면 이 목록에 추가하고, 구독을 취소하면 목록에서 삭제합니다.

신문사에서 새로운 신문을 발행하면 목록에 등록되어있는 구독자들에게 새 신문을 배달합니다.

옵저버 패턴도 이 메커니즘과 동일한 방식으로 동작합니다.

Observer Pattern을 가장 간단하게 구현한 구조를 신문 구독 서비스에 빗대어 설명해 보도록 하겠습니다.

Subject(주제) 객체란 정보를 제공하는 객체이며, 신문사에 해당됩니다.

Observer(옵저버) 객체는 정보를 필요로 하는 객체이며, 구독자에 해당됩니다.

ObserverList는 옵저버 객체들을 관리하는 리스트이며, 구독자에 관한 목록에 해당됩니다.

registerObserver(), removeObserver()는 목록에서 옵저버 객체들을 추가/삭제하는 기능을 수행합니다.

notifyObservers()는 목록에 있는 옵저버들에게 값이 갱신되었음을 알리는 기능을 수행하며, 구독자들에게 새 신문을 배달하는 것에 해당됩니다.

여기서 Key point는 인터페이스를 이용하여 두 객체간의 결합을 느슨하게 만들었다는 점에 있습니다. 주제는 옵저버가 특정 인터페이스(Observer Interface)를 구현한다는 것 뿐만 알고 있습니다.

이렇듯 느슨한 결합으로 얻을 수 있는 이점은 다음과 같습니다.

첫째, 옵저버는 언제든지 새로 추가할 수 있습니다.

둘째, 새로운 형식의 옵저버를 추가하려고 할 때도 주제를 전혀 변경할 필요가 없습니다.

셋째, 주제와 옵저버는 서로 독립적으로 재사용할 수 있습니다.

넷째, 주제나 옵저버가 바뀌더라도 서로한테 영향을 미치지는 않습니다.

이런 이점들을 통해 애플리케이션은 변경 사항에 대하여 유연하게 대처할 수 있게 됩니다.

이제는 기상 모니터링 애플리케이션에 Observer Pattern을 적용시켜 보도록 하겠습니다.

public class WeatherData implements Subject{
    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
        temperature = 0;
        humidity = 0;
        pressure = 0;
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObserverPull() {
        for (Observer observer : observers) {
            observer.updatePull();
        }
    }

    @Override
    public void notifyObserverPush() {
        for (Observer observer : observers) {
            observer.updatePush(this.temperature, this.humidity, this.pressure);
        }
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
public class StatisticsDisplay implements Observer{
    private WeatherData weatherData;
    private float temperature;
    private float humidity;
    private float pressure;

    public StatisticsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
    }

    @Override
    public void updatePull() {
        temperature = weatherData.getTemperature();
        humidity = weatherData.getHumidity();
        pressure = weatherData.getPressure();

        display();
    }

    @Override
    public void updatePush(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;

        display();
    }

    public void display() {
        // 기상 통계 정보를 화면에 출력
    }
}

기존의 코드에서 WeatherData 객체를 Subject 객체로, 화면 객체를 Observer 객체로 바꾸고 옵저버 객체들을 관리하는 리스트를 만들었습니다.

기존의 measurementsChanged()의 역할은 notifyObserver()가 수행합니다.

그런데 여기서 notifyObserver()PushPull 두 종류로 나뉘어져 있는것을 볼 수 있습니다.

그 이유는 정보가 전달되는 방식의 차이때문입니다.

주제 객체가 옵저버 객체에게 직접 정보를 전달해 주는 방식을 Push라고 하고, 주제 객체가 옵저버 객체들에게 알림을 보내 알림을 받은 옵저버 객체들이 직접 주제 객체의 정보를 가져오는 방식을 Pull이라고 합니다.

쉽게 말해 신문 구독 서비스로 예를 들어보면 Push 방식은 신문사에서 새로운 신문이 발행되면 구독자들에게 직접 새 신문을 배달하는 방식입니다.

Pull 방식은 신문사가 구독자들에게 '새로운 신문이 발행되었으니 알아서 가져가세요'라고 알려 구독자들이 직접 신문사로 찾아가 새 신문을 가져오는 방식입니다.


패턴 정의


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

profile
#취준생 #Back-end

0개의 댓글