02. 옵저버 패턴

Mando·2023년 3월 17일
0

디자인 패턴

목록 보기
2/6

기상 모니터링 애플리케이션 알아보기

  • 기상 스테이션(기상 정보를 수집하는 물리 장비)
  • WeatherDate 객체(기상 스테이션으로부터 오는 정보 객체)
  • 사용자에게 현재 기상 정보를 보여주는 디스플레이 장비(현재는 3개의 항목이 있고, 더 확장 가능하다. 또한, WeatherDate객체가 갱신 될 때마다 디스플레이도 업데이트가 된다.)

WeatherDate 클래스 알아보기

기상 스테이션으로부터 갱신된 정보를 가져오는 것은 WeatherDate 객체가 알아서 한다고 가정하자.

우리가 여기서 집중을 해야할 부분은 WeatherDate에서 온도, 습도, 기압 값을 새로 받아 갱신될 때마다 measurementsChanged()메서드를 호출해서 디스플레이를 업데이트해야한다는 것이다.

/*
 * 기상 관측값이
 * 갱신될 때마다
 * 이 메소드가 호출됩니다.
 *
 */
public void measurementsChanged() {
	// 디스플레이를 업데이트 하는 코드
}

구현 목표

  • WeatherDate 클래스는 3가지 측정값(온도, 습도, 기압)의 getter가 있다.
  • 새로운 기상 측정 데이터가 들어올 때마다 measurementsChanged()메서드가 호출된다.(어떻게 호출되는지는 알 필요가 없으며, 그냥 이 메소드가 호출되나는 사실만 알면 된다.(
  • 기상 데이터를 사용하는 디스플레이 3가지를 구현해야 한다. WeatherDate에서 새로운 측정값이 들어올 때마다 디스플레이를 업데이트 해야한다.
  • 따라서 measurementsChanged()메서드에 디스플레이를 업데이트 하는 코드를 추가해야 한다.

지금은 디스플레이 3가지로 제한을 두었지만, 확장성을 고려해 딛스플레이 요소를 더하거나 뺄 수 있는 것도 고려해야 한다.

기상 스테이션용 코드 추가하기

public class WeatherData {

public void measurementsChanged() {
	
    //WeatherData에 있는 getter 메서드를 통해 최근 측정값을 가져온다.
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure(); 

	//각 디스플레이 갱싱
    //최근 측정값을 전달하면서 update 메서드를 호출
    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure); 
    }
}

원칙으로 추가 코드 살펴보기

어떤 걸 위반하고 어떤 걸 제대로 따랐을까?

currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
  • 디스플레이와 갱신된 값을 주고 받는데 update라는 공통된 인터페이스를 사용한다.

  • 하지만, currentConditionDisplay처럼 구체적인 구현에 맞춰서 코딩했으므로 프로그램을 고치지 않고서는 다른 디스플레이 항목을 추가하거나 제거할 수 없다.

  • 즉, 바뀔 수 있는 부분을 캡슐화 하지 않았다.

실행 중에 디스플레이를 더하거나 빼려면 어떻게 해야할까요?(바뀔 수 있는 부분은 디스플레이 부분을 캡슐화해야한다.)

옵저버 패턴 이해하기

신문사(주제 : subject) + 구독자(옵저버 : observer) = 옵저버 패턴

옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many)의존성을 정의한다

  • 옵저버 객체들은 주제를 구독하고 있으므로 주제 데이터가 바뀌면 갱신 내용을 전달받는다.
  • 옵저버 객체에서 빠지거나 추가될 수 있다.(디스플레이가 추가되거나 빠질 수 있다.)
  • 하지만, 옵저버 객체가 아니면 주제 데이터가 바뀌어도 아무 연락을 받지 못한다.(제거된 디스플레이는 갱신된 내용을 전달받을 필요가 없다.)

옵저버 패턴의 구조

보통은 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현한다.

  • Subject interface

  • ConcreateSubject
    * 옵저버로 등록하거나 옵저버 목록에서 탈퇴시키는 책임

    • 주제 객체의 상태가 바뀔 때마다 모든 옵저버에게 연락을 돌리는 책임(notifyObserver)
  • Observer interface

  • ConcreateObserver
    * 주제 객체가 옵저버에게 주제 객체의 책임이 바뀌었다고 연락을 돌릴 때 연락을 받을 책임(update)

기상 스테이션 구현하기

Subject interface 정의

public interface Subject {
  public void registerObserver(Observer o;
  public void removeObserver(Observer o);
  public void notifyObservers();
}

Observer interface 정의

public interface Observer {
  public void update(float temp, float humidity, float pressure);
}

DisplayElement 정의

public interface DisplayElemet{
	public void display();
}

인터페이스 구현

WeatherData 구현하기(Subject 구현체)

public class WeatherData implements Subjet{
	
	private List<Observer> observers;
	private float temperature;
	private float humidity;
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

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

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

	//모든 옵저버에게 상태 변화를 알려주는 부분
    //모든 옵저버는 update 인터페이스를 가지고 있으므로 update를 호출해 쉽게 상태변화를 알려줄 수 있다.
	@Override
	public void notiyObserver() {
		for(Observer observer : observers) {
			observer.update(temperature, humidity, pressure);
		}
	}

	public void measurementsChanged() {
    	// 측정값이 변했으므로 옵저버에게 알림
		notiyObserver();
	}
	
	public void setMeasurements(float temperature, float humidity, float pressure) {
    	// 기상 스테이션으로부터 측정값을 받는 로직
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
        measurementsChanged();
	}
	
	//기타 여러 메속드들...

}

CurrentConditionDisplay 구현하기(Display의 구현체)

public class CurrentConditionsDisplay implements Observer, DisplayElement {
	private float temperature;
	private float humidity;
	private WeatherData weatherData;
	
	public CurrentConditionsDisplay(WeatherData weatherData) {
    	//이 옵저버의 subject
		this.weatherData = weatherData;
        //subject에 구독 옵저버로 등록
		weatherData.registerObserver(this);
	}
	
	public void update(float temperature, float humidity, float pressure) {
    	//측정값이 변경되면 subject가 update를 호출한다. 
        //그럼 측정값이 변경되었으므로 display도 갱신한다.
		this.temperature = temperature;
		this.humidity = humidity;
		display();
	}
	
	public void display() {
		System.out.println("현재 상태: 온도 " + temperature  + "F, 습도 " + humidity + "%");
	}
}
public class WeatherStation {

	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
        //이 옵저버의 subject로 weatherData를 등록합니다.
		CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);

		//값이 변경될 때마다 notiyObserver가 호출되어 subject(WeatherData)의 옵저버들에게 update 인터페이스로 객체의 상태가 변경되었다는 알림이 간다.
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

지금까지 옵저버 패턴을 구현한 방식은 push(subject가 observer로 데이터를 보내는 방ㄷㅇ식)이다.

이번에는 pull(observer가 subejct로부터 데이터를 가져오는 방식)으로 옵저버 패턴을 구현해보겠다.

그 전에 push 방식을 이용해 옵저버 패턴을 구현했을 때 문제점에 대해 알아보자
1. subject의 특정 데이터를 대부분의 observer에서 사용하지 않는다고 해도 update메서드의 파라미터로 해당 데이터를 보내야한다.
2. 따라서 observer는 자신에게 관심이 없는 데이터라도 무조건 받아야 한다.

이러한 방식은 subject가 자신의 데이터에 대한 getter 메서드를 가지고 subject가 필요한 데이터를 가져다가 쓸 때 getter메서드를 호출하게 하면된다.

Pull 방식으로 옵저버 패턴 구현하기

push 방식을 이용해 옵저버 패턴을 구현했을 때는 subject가 update를 호출하면서 subject 객체 데이터를 전달했지만, 이번에는 observer에게 값이 변했다는 알림만 전달한다.

public class WeatherData implements Subjet{
	//...

	@Override
	public void notiyObserver() {
		for(Observer observer : observers) {
			observer.update();
		}
	}
	
	//...

	public float getTemperature() {
		return temperature;
	}
	
	public float getHumidity() {
		return humidity;
	}
	
	public float getPressure() {
		return pressure;
	}
}
public interface Observer{
	public void update();
}

이후 observer는subject의 getter 메서드를 호출해서 필요한 값을 당겨온다.

public class CurrentConditionsDisplay implements Observer, DisplayElement {
	
	//...
	
	public void update() {
		this.temperature = weatherData.getTemperature();
		this.humidity = weatherData.getHumidity;
		display();
	}

	//...

}

정리

  • 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 정보가 갱신되는 1:N 의 관계를 정의한다.

  • 옵저버 패턴은 주제와 옵저버가 느슨하게 결합되어있는 객체 디자인을 제공한다.

  • 주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현 한다는 것 뿐이다.

  • 옵저버는 언제든지 새로 추가할 수 있음. (주제는 Observer인터페이스 구현하는 객체 목록에만 의존하기때문)

  • 주제나 옵저버가 바뀌더라도 서로에게 전혀 영향을 주지않는다.

  • 그래서 주제와 옵저버는 서로 독립적으로 재 사용할수 있다.

  • 느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할수 있다. (객체 사이의 상호 의존성을 최소화 할 수 있기 때문)

  • 새로운 형식의 옵저버를 추가하려해도 주제를 전혀 변경할 필요가 없음. (새로운 클래스에서 Observer 인터페이스만 구현해주면됨)

0개의 댓글