반응형
옵저버 패턴 이해를 위한 간단한 문제 제안
- 독자에게 기상 조건인 온도, 습도, 기압 3가지의 변화를 감지하는 시스템을 만들라는 요구사항이 들어옵니다.
- 위의 3가지 항목들은 실시간으로 갱신되야 합니다.
- 디스플레이를 손쉽게 추가할 수 있어야 합니다.이 기기는 다른 추가사항 또한 바로 넣을 수 있도록 확장 가능해야 합니다.
- 요구 사항을 좀 더 자세히 설명해보겠습니다.
- 저희들이 구현해야하는 것은 WeatherData 객체입니다.
- 가상 스테이션은 습도, 온도, 기압 센서로부터 데이터를 받습니다. 이때 변경되는 데이터는 실시간으로 받기 때문에 WeatherData객체는 실시간으로 데이터를 변경해줘야합니다.
- WeatherData객체는 가상 스테이션과 통신해서 기상 데이터를 가져옵니다.
- WeatherData객체는 디스플레이 장비에 현재 조건, 기상통계, 기상 예보중 원하는 내용을 선택해서 표시할 수 있습니다.
클래스로 살펴보기
- 위의 WeatherData 객체의 get 메소드는 각각의 기상정보를 가져오는 메소드입니다.
- weatherData에서 갱신된 값을 가져올때마다 mesarementsChanged() 메소드가 실행됩니다.
- 현재 조건, 기상 통계, 기상 예보를 보여주는 3가지 디스플레이가 업데이트 되도록 아mesarementsChanged()를 변경하도록 해보겠습니다.
문제점 발생
public class WeatherData extends Observable {
public void measurementsChanged() {
//메소드를 호출해서 측정값을 가져온다.
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
//각 디스플레이를 갱신
currentConditionDisplay.update(tempm, humidity, pressure);
statisticDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}
- 이렇게 있는 그대로를 구현했는데 아래와 같은 문제가 존재합니다.
- 구체적인 구현에 맞췄기 때문에 다른 디스플레이를 추가하거나 제가할 때 코드를 변경해야 합니다.
- 디스플레이를 갱신하는 3가지 메소드 모두 각은 인터페이스를 사용중이지만 실행중에 디스플레이를 빼거나 더할 수있게 하기위해서는 어떻게 해야할 지 모르겠습니다.
- 그러므로 캡슐화의 필요성이 있어보입니다.
- 옵저버 패턴을 도입하면서 위의 문제점들을 해결해보겠습니다.
옵저버 패턴 도입
- 옵저버 패턴의 성격은 일대단 의존성을 가지며 정의는 한 객체가 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식입니다.
- 옵저번 패턴은 신문사에 비유하면 이해하기 쉽습니다.
- 신문사와 구독자는 신문사를 구독하는 관계를 유지한다면 신문사는 신문을 만들때만 구독자에게 알려주어 구독자가 신문을 읽을수 있게 하면 됩니다.
- 이때 구독하는 대상이 되는 것을 주제(ex.신문사)라 부릅니다.
- 구독을 하는 주체를 옵저버(ex.구독자)라 부릅니다.
느슨한 결합이란
- 느슨한 결합은 모듈별로 서로가 잘모르는 관계를 의미합니다.
- 옵저버 패턴에서의 느슨한 결합의 예시는
- 주제는 옵저버가 특정 인터페이스를 구현한다는 사실만 압니다.
- 옵저버는 언제든지 새로 추가할 수 있습니다.
- 주제는 옵저버 인터페이스에만 의존하므로 언제든지 새로운 옵저버를 추가할 수 있다.
- 또한 다른 옵저버로도 쉽게 변경이가능하며 제거도 가능하다.
- 새로운 형식의 옵저버를 추가할 때도 주제블 변경할 필요가 없다.
- 주제와 옵저버는 서로 독립적으로 재사용이 가능하다.
- 주제나 옵저버가 달려져도 서로에게 영향을 미치지 않는다.
- 상호작용하는 개체 사이에는 가능하면 느슨한 결합을 사용해야 합니다.
옵저버 패턴을 활용한 가상 스테이션 설계하기
- 앞서 배운 전략패턴을 응용해서 각각의 기능을 나타내는 최소한의 인터페이스로 정의를 했습니다.
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
- Subject는 Observer를 인자로 받고 각각의 Observer가 실행하는 3개의 메소드를 정의했습니다.
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
- Observer에는 기상 정보가 변경되었을 때 옵저버에게 전달되는 상태값이다.
public interface DisplayElement {
public void display();
}
- DisplayElement는 항목을 표시하는 display() 메소드 밖에 없다.
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
//옵저버 등록 요청 메소드
public void registerObserver(Observer o) {
observers.add(o);
}
//옵저버가 탈퇴를 요청하면 List에서 빼기만 하면 된다.
public void removeObserver(Observer o) {
observers.remove(o);
}
//모든 옵저버에게 상태 변화를 알려 주는 부분.
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
//가상 스테이션으로부터 갱신된 측정값을 등록된 옵저버에게 알리는 메소드
public void measurementsChanged() {
notifyObservers();
}
//테스트를 위한 가상데이터를 설정하느 메소드
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
//기타 메소드
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
- WeatherData를 구현합니다.
- WeatherData는 Subject를 구현합니다.
- 자세한 설명은 코드에 주석을 첨부하였습니다.
디스플레이 요소 구현하기
//화면 설정 정보를 갱신하기 위해 Observer를 정의, 정보를 보여주기 위해 DisplayElement를 정의
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
//생성자에 WeatherData 객체를 생성할때 전달하며 Observer에 등록한다.
public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
//update를 호출하며 온도와 습도를 저장하고 display()를 호출한다.
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
//가장 최근에 받은 온도와 습도를 출력한다.
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
- CurrentConditionsDisplay 객체를 구현합니다.
- CurrentConditionsDisplay는 현재의 화면에 대한 설정 정보를 갱신해야 하므로 Observer를 정의합니다.
- 또한 보여주는 기능을 실행해야하기 때문에 DisplayElement를 정의합니다.
- 이외의 기능은 코드에 주석을 첨부하였습니다.
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
- 응용해서 평균 최저를 보여주는 StatisticsDisplay를 구현합니다.
- 응용해서 기상예보를 보여주는 ForecastDisplay를 구현합니다.
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay =
new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
weatherData.removeObserver(forecastDisplay);
weatherData.setMeasurements(62, 90, 28.1f);
}
}
- 테스트를 위한 WeatherStation입니다.
실행결과
정리
- 바뀌는 부분은 캡슐화해야 합니다.
- 상속보다는 구성을 활용해야합니다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍해야 합니다.
- 상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용해야 합니다.
- 이는 주제와 옵저버간의 관계의 결합도를 낮춰야한다는 의미입니다.
반응형
'디자인 패턴' 카테고리의 다른 글
CHAPTER 05.싱글톤 패턴 (0) | 2023.05.06 |
---|---|
CHAPTER 04.팩토리 패턴 (0) | 2023.05.06 |
CHAPTER 03.데코레이터 패턴 (0) | 2023.01.22 |
CHAPTER 01.디자인 패턴 소개와 전략 패턴 (2) | 2023.01.22 |
CHATER 00.이 책을 읽는 방법 (0) | 2023.01.22 |