옵저버 패턴(Observer Pattern)
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용을 갱신해주는 패턴으로써, 일대다 의존성을 정의하는 패턴이다. 어느 한 객체가 다른 객체의 상태를 구독해야 하는 경우 효과적이다.
기상 스테이션 시스템 구축 프로젝트
Weather-O-Rama라는 업체로부터 날씨 정보를 표시하는 디스플레이 애플리케이션을 일을 수주하게 되었다.
Weather-O-Rama에서 요구하는 기능은 아래와 같다.
- 실시간으로 변화하는 날씨 정보(상태, 온도, 습도, 기압 등)를 표시하는 기능이 있어야 함
- 추후 이러한 디스플레이 기능을 다른 개발자들도 추가할 수 있어야 함
기본적으로 Weather-O-Rama는 WeatherData라는 클래스를 제공해주고 있으며, 날씨 정보가 변경되면 그 클래스의 measurementsChanged()라는 메서드가 호출된다고 한다.
public class WeatherData {
// ...
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
}
}
이 상황에서 서로 다른 3개의 디스플레이 객체들의 상태를 변경해주려면 아래와 같이 구현할 수 있다.
public class WeatherData {
// ...
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 서로 다른 3개의 디스플레이의 상태 업데이트
currentConditionDisplay.update(temp, humidity, pressure);
statisticDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}
하지만 위와 같이 구현하면, WeatherData라는 객체는 자신을 구독하고 있는 디스플레이 객체들을 미리 알고 있어야 하므로, 구독하고 있는 디스플레이 객체가 바뀌거나 추가됐을 때 WeatherData 객체 코드를 계속 수정해줘야 한다.
(즉, 바뀌는 부분이 캡슐화되어 있지 않기 때문에 좋은 설계 방법은 아니다. 위 Display 객체들과 같이 추후 변경될 가능성이 있는 요소들은 구현체를 사용하는 것이 아니라 interface를 활용해서 캡슐화 하는 것이 좋다)
이제 WeatherData, 각 Display 객체들을 옵저버 패턴으로 만들어 보자!
우선 Subject(구독받는 객체), Observer(구독하는 객체)를 interface로 만들어 두고...
public interface Subject {
public void registerObserver(Observer o); // 구독 신청
public void removeObserver(Observer o); // 구독 해지
public void notifyObservers(); // 구독 중인 옵저버들에게 전달
}
public interface Observer {
// Subject가 보내준 상태값으로 업데이트
public void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
public void display(); // 상태값을 화면에 표시
}
이제 Subject 인터페이스를 구현해서 WeatherData 클래스를 캡슐화한다.
public class WeatherData implements Subject {
public List<Observer> observers;
public float temperature;
public float humidity;
public 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 o : observers) {
o.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();
}
}
다음으로 Observer 인터페이스를 구현해서 디스플레이 구현체를 만들고...
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current temperature: " + temperature + "F, humidity: " + humidity + "%");
}
}
마지막으로 실행해보면...
public class Main {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
weatherData.registerObserver(currentConditionDisplay);
weatherData.setMeasurements(80, 70, 60);
}
}
위와 같이 인터페이스를 사용해서 구현하면, Subject 구현체는 정확한 내용은 모르지만 Observer 구현체가 update()라는 메서드를 가지고 있고, 상태가 변경되었을 때 update() 메서드를 호출하면 된다라는 것만 알고 있으면 되기 때문에, 객체 간의 결합을 느슨하게 만들 수 있다.
이렇게 옵저버 패턴은 Subject와 Observer 객체가 일대다 관계로 이루어져 있고, Subject의 상태가 변경되었을 때 Observer들에게 알려주는 패턴이며, JavaScript의 이벤트나 스위프트의 key-value 옵저핑 프로토콜에도 쓰이는 것을 확인할 수 있다.
옵저버 패턴에선 상태가 변경될 때마다 옵저버들에게 알려주는 것이 아니라, 옵저버가 Subject의 값을 필요로 할 때에만 getter() 함수를 사용해서 상태값을 가져오는 풀(Pull) 방식으로 구현할 수도 있다. 상황에 따라 풀 방식이 더 효율적일 수 있으므로 참고하면 좋다.
옵저버 패턴에서 배울 수 있는 객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
옵저버 패턴(Observer Pattern)
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용을 갱신해주는 패턴으로써, 일대다 의존성을 정의하는 패턴이다. 어느 한 객체가 다른 객체의 상태를 구독해야 하는 경우 효과적이다.
'Fundamental > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 4. 팩토리 패턴(Factory Pattern) (0) | 2024.09.10 |
---|---|
[디자인 패턴] 3. 데코레이터 패턴(Decorator Pattern) (1) | 2024.09.04 |
[디자인 패턴] 1. 전략 패턴(Strategy Pattern) (0) | 2024.08.29 |
Multi-Tier Architecture란? (0) | 2021.11.27 |
소프트웨어 아키텍처 패턴(Software Architectural Pattern) (0) | 2021.11.21 |