옵저버 패턴의 정의
옵저버 패턴(Observer Pattern)은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.
주체 (One, 상태를 저장하고 있는 객체) : 옵저버 (Many, 주체에 딸린 객체)
옵저버 패턴 UML

주제 인터페이스와 옵저버 인터페이스를 두고, 이를 구상 클래스가 구현한다. 주제 구상 클래스의 경우 옵저버 등록 및 삭제, 그리고 상태가 바뀔 때마다 모든 옵저버에게 연락하는 메소드를 구현한다, 그리고 옵저버 구상 클래스의 경우 업데이트를 통해 주제의 정보를 받을 수 있다.

주체 인터페이스를 구현한 주제 구상 클래스는 옵저버 인터페이스를 구현한 옵저버 구상 클래스들의 정보를 가지고 있으면서, 상태가 변경될 때마다 모든 옵저버 구상 클래스들에게 연락한다. (notifyObservers()가 등록된 모든 옵저버들의 update()를 호출하는 방식으로 진행됨)
옵저버 패턴 예제 코드
public interface Observer {
public void update();
}
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
Observer 인터페이스를 구현하는 클래스들은 옵저버 클래스로, Subject 인터페이스를 구현하는 클래스들은 주제 클래스가 된다.
/**
* Push(주제가 옵저버에세 상태를 알리는 방식)에서 Pull(옵저버가 주제로부터 상태를 끌어오는 방식)으로 변경되었음
*/
public class WeatherData implements Subject {
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);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
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 클래스는 Subject 인터페이스를 상속한 주제 클래스이며, registerObserver(), removeObserver(), notifyObserver()를 통해 옵저버들을 관리한다.
또한 Push 방식으로 주제가 옵저버들에게 상태를 전달하는 방식이 아닌, Pull 방식으로 옵저버들이 주제로부터 상태를 알아서 끌어오는 방식으로 이루어져있다.
setMeasurements() 메소드가 호출되어 주제의 상태가 업데이트되면, measurementChanged(), notifyObservers()가 연쇄적으로 호출되어 옵저버들의 update() 메소드가 호출된다. 이 때, 옵저버들은 주제 클래스의 getter 메소드(getTemperature(), getHumidity(), getPressure())로 원하는 값만 선택적으로 가져올 수 있다.
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() {
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
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");
}
}
}
ForecastDisplay는 Observer 인터페이스를 구현한 옵저버 클래스이며, 생성자에서 WeatherData 주제를 등록하고, 이 때 자신을 옵저버로 등록한다.
WeatherData 주제의 업데이트를 통해 update() 메소드가 호출되면, 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);
HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
옵저버 클래스들인 OOODisplay 클래스들은 WeatherData 주제 클래스의 인스턴스를 생성자 파라미터로 받아 옵저버로 등록된다.
이후 WeatherData 주제에서 업데이트(setMesaurements())가 발생하면 옵저버들의 update()가 호출되어, 각자 원하는 데이터를 받아와 사용(Display())하게 된다.
Publish-Subscribe Pattern 과 Observer 패턴의 차이점
- Observer패턴은 Observer와 Subject가 서로를 인지하지만 Pub-Sub패턴의 경우 서로를 전혀 몰라도 상관없음.
- Observer패턴에 비해 Pub-Sub패턴이 더 결합도가 낮음. (Loose Coupled)
- Observer패턴은 대부분 동기(synchronous) 방식으로 동작하나 Pub-Sub패턴은 대부분 비동기(asynchronous) 방식으로 동작함.
- Observer패턴은 단일 도메인 하에서 구현되어야 하나 Pub-Sub패턴은 크로스 도메인 상황에서도 구현 가능함.
느슨한 결합의 위력
느슨한 결합(Loose Coupling)은 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계. 느슨한 결합을 활용하면 유연성이 아주 좋아진다.
옵저버 패턴에서의 느슨한 결합
- 주제는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 암.
- 옵저버는 언제든지 새로 추가할 수 있음.
- 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없음.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지는 않음.
객체지향 원칙
디자인 원칙 4: 상호작용하는 개체 사이에는 가능하면 느슨한 결합을 사용해야 한다.
라이브러리 속 옵저버 패턴 알아보기
스윙 라이브러리와 같은 GUI 프레임워크는, 옵저버(리스너, Listener)를 통해 GUI 구성요소에서 일어나는 다양한 유형의 이벤트를 감지한다.
핵심 정리
- 옵저버 패턴은 객체들 사이에 일대다 관계를 정의함.
- 주제는 동일한 인터페이스를 써서 옵저버에게 연락함.
- Observer 인터페이스를 구현하기만 하면 어떤 구상 클래스의 옵저버라도 패턴에 참여할 수 있음.
- 주제는 옵저버들이 Observer 인터페이스를 구현한다는 것을 제외하면 옵저버에 관해 전혀 모름. 따라서 이름 사이의 결합은 느슨한 결합(Loose Coupling)임.
- 옵저버 패턴을 사용하면 주제가 데이터를 보내거나(Push 방식) 옵저버가 데이터를 가져올(Pull 방식) 수 있습니다(일반적으로 Pull 방식이 더 ‘옳은’ 방식이라고 간주함.)
- 스윙은 다른 여러 GUI 프레임워크와 마찬가지로 옵저버 패턴을 많이 사용함.
- RxJava, 자바빈, RMI 외에 코코아나 스위프트, 자바스크립트와 같은 다른 언어의 프레임워크에서도 옵저버 패턴을 많이 사용함.
- 옵저버 패턴은 여러 개의 주제와 메시지 유형이 있는 복잡한 상황에서 사용하는 Publish-Subscribe 패턴과 친척임.
- 옵저버 패턴은 자주 쓰이는 패턴으로, 모델-뷰-컨트롤러(MVC)와 연관이 있음.
GitHub Code Reference: https://github.com/Krapi0314/Design_Patterns
GitHub - Krapi0314/Design_Patterns: Java Implementation of GoF's Design Patterns
Java Implementation of GoF's Design Patterns. Contribute to Krapi0314/Design_Patterns development by creating an account on GitHub.
github.com
Reference: http://www.yes24.com/Product/Goods/108192370
헤드 퍼스트 디자인 패턴 - YES24
유지관리가 편리한 객체지향 소프트웨어 만들기!“『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나
www.yes24.com
'객체지향 및 기반 언어 > 디자인 패턴' 카테고리의 다른 글
[Design Pattern] Chapter 06 커맨드 패턴 (0) | 2022.10.02 |
---|---|
[Design Pattern] Chapter 05 싱글턴 패턴 (0) | 2022.10.02 |
[Design Pattern] Chapter 04 팩토리 패턴 (0) | 2022.10.01 |
[Design Pattern] Chapter 03 데코레이터 패턴 (0) | 2022.10.01 |
[Design Pattern] Chapter 01 디자인 패턴 소개와 전략 패턴 (0) | 2022.09.25 |