샘플 요구 사항
기상 관측이 업데이트될 때마다 세 가지 표시 항목(현재 상태 표시, 기상 통계 표시, 일기예보 표시)을 업데이트해야 합니다.
// 구체적인 구현 -> 확장에 유연하지 못한 코드
public class WeatherData {
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
}
// 바뀔 수 있는 부분 -> 캡슐화 필요, 디스플레이 항목과 데이터 주고받는 관계에서 공통 인터페이스
currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
관찰자 패턴
한 개체의 상태가 변경되면 다른 종속 개체에 알림이 전송되고 자동으로 업데이트되도록 일대다 종속성을 정의합니다.
- 주제(1) : 관찰자(들)
- 주제에는 연관된 관찰자가 있으며 주제의 상태가 변경되면 정보가 관찰자에게 전달됩니다.
- 일반적으로 주체 인터페이스와 관찰자 인터페이스를 포함하는 클래스 디자인으로 구현
즉, 주체는 상태를 저장하고 제어하며 관찰자는 상태를 사용하지만 반드시 소유하지는 않습니다.
또한 다수의 옵저버가 있을 수 있으며, 그들은 주체 의존적이며 주체가 자신의 상태가 변경되었음을 알리기를 기다립니다(예: 이벤트 리스너도 옵저버 패턴의 일부입니다).
느슨한 결합
객체가 상호 작용할 수 있지만 서로를 잘 알지 못하는 관계
- 주체는 관찰자가 특정 인터페이스(관찰자 인터페이스)를 구현한다는 사실만 알고 있습니다.
- 관찰자는 언제든지 추가할 수 있습니다.
- 주제는 Observer 인터페이스를 구현하는 객체 목록에만 의존합니다.
- 주제는 Observer 인터페이스를 구현하는 객체 목록에만 의존합니다.
- 새 옵저버 유형을 추가해도 주제를 변경할 필요가 없습니다.
- 마찬가지로 새 클래스에 관찰자 인터페이스를 구현하고 관찰자로 등록하기만 하면 됩니다.
- 마찬가지로 새 클래스에 관찰자 인터페이스를 구현하고 관찰자로 등록하기만 하면 됩니다.
- 주제와 관찰자는 독립적으로 재사용할 수 있습니다.
- 모티브나 보는 사람이 바뀌어도 서로 영향을 주지 않는다.
설계 원칙
- 가능할 때마다 상호 작용하는 개체 간에 느슨한 결합을 사용해야 합니다.
느슨하게 결합된 디자인을 사용하면 변경 사항이 발생할 때 이를 처리할 수 있는 유연한 객체 지향 시스템을 만들 수 있습니다.
2 구현
구현 방식의 차이지만 일반적으로 옵저버가 필요한 데이터를 선택해서 가져오도록 하려면 PULL 방식을 사용하는 것이 좋습니다.
이렇게 하면 나중에 더 쉽게 확장할 수 있습니다.
1. PUSH 방식
주체와 관찰자
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
주제 구현 클래스
public class WeatherData implement 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);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
for (Observer observer: observers) {
opserver.update(temperature, humidity, pressure);
}
}
// 갱신된 측정값을 받으면 실행되는 메서드
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
관찰자 구현 클래스
public class CurrentConditionDisplay implement Observer {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("~~");
}
}
2. PULL 방식
주제에는 데이터에 대한 getter 메서드가 있으며 관찰자는 필요한 데이터를 얻을 때 해당 메서드를 호출합니다.
관찰자 인터페이스
인수 없이 호출하도록 메서드 변경
public interface Observer {
public void update();
}
주제 구현 클래스
게터 메서드 추가
public class WeatherData implement Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public void notifyObservers() {
for (Observer observer: observers) {
opserver.update();
}
}
public float getTemperature() {
return this.temperature;
}
public float getHumidity() {
return this.humidity;
}
public float getPressure() {
return this.pressure;
}
}
관찰자 구현 클래스
주제에 구현된 getter 메서드를 호출합니다.
public class CurrentConditionDisplay implement Observer {
// ...
private WeatherData weatherData;
public void update(float temp, float humidity, float pressure) {
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
옵저버 패턴에 적용된 디자인 원칙
애플리케이션에서 달라지는 부분을 찾아 그렇지 않은 부분과 분리합니다.
- 응용 프로그램의 다른 부분: 주제의 상태, 관찰자 수
- 응용 프로그램에서 변경되지 않는 부분: 주제 자체는 변경할 필요가 없습니다.
구현 대신 인터페이스하는 프로그램.
- 주제와 관찰자는 인터페이스에서 분리됩니다.
상속 대신 구성을 사용하십시오.
- 주제 내 관찰자 목록을 구성하고 관리합니다.