티스토리 뷰
Observer Pattern 정의
한 객체(Subject, Observable)의 상태가 바뀌면 그 객체에 의존하는 다른 객체들(Observers)한테 연락(notify)이 가고 자동으로 내용이 갱신(update)되는 방식으로 일대다 (one-to-many) 의존성을 정의한다.
즉, 관찰 대상인 주제(Subject)가 변경되면, 등록된 Observer 들에게 notify를 주고, Observer들은 이벤트를 받아 처리한다.
Observer Pattern 구조
아래는 Head First Design Pattern 에 나오는 기상청에서 받아온 데이터를 출력해주는 프로그램에 Observer Pattern을 적용한 예제이다.


Subject인 WeatherData 클래스가 있고 서로 다른 Display를 보여주는 DisplayElement(Observer)인 StatisticsDisplay와 CurrentConditionDisplay가 존재한다.
처음에 WeatherData 클래스의 메서드 registerObserver를 사용해서 각 Observer들을 등록해 두면, WeatherData에서 변경이 생길 때마다 notifyObservers 메서드 내부에서 ArrayList<Observer> 들의 update를 호출해주고, 각 Observer들은 이를 처리하는 구조이다.
실생활에 빗댄 예제 중 Head First Design Pattern 책에서 해주는 설명 중 신문사 구독 예제 설명이 이해하기 가장 쉬웠다. Subject(Publisher) 와 Observer(Subscriber)로 구성된 신문사와 구독자의 관계를 살펴보면,
- 소비자A는 신문사에 구독을 신청한다.
- 신문이 나오면 신문사는 소비자A에게 신문을 배달한다.
- 소비자A가 신문사에 구독 서비스를 취소한다.
- 신문이 나와도 소비자A는 신문을 배달받지 못한다.
이를 code level로 다시 살펴보면,
- registerObserver method를 통해서 observers list Observer (소비자A)객체를 저장한다.
- notifyObservers method를 통해서 observers list 들의 update method들을 호출한다.
- Observer (소비자A) 객체에 대해서 removeObserver method를 통해서 observers list에서 제거한다.
- notifyObservers method가 호출되어도 Observer (소비자A)는 update method가 호출되지 않는다.
옵저버 패턴에서는 Subject (Observable)와 Observer가 느슨하게 결합되어 있는 객체 디자인을 제공한다고 하는데, 그 이유를 알아보자.
Subject 입장에서는 Observer에 대해 아는 것은 Observer가 특정 인터페이스를 구현한다는 사실뿐이다. (Observer Interface)
Observer에 대해서 언제든지 새로 추가할 수 있다. (제거 또한 마찬가지)
새로운 형식의 Observer를 추가하려고 할 때도 Subject를 전혀 변경할 필요가 없다.
이는 Interface Observer에 대해서 update method를 호출하기로 미리 약속해두었기 때문에, 해당 부분만 맞춰주면, 다른 부분의 변경사항에 대해서는 Subject와 Observer가 서로 영향을 미치지 않는 느슨하게 결합된 구조를 갖게 된다는 것을 의미한다.
Observer 패턴 예제 코드
Head First Design Pattern에 수록된 예제 코드이다.
Subject Interface
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);
}
Subject 인터페이스와 Observer 인터페이스를 정의해 둔다. Subject 인터페이스에는 Observer를 등록하기 위한 registerObserver 메서드와 제거를 위한 removeObserver를 가지고 있다. 또한, Subject내에서 변경 등에 대해 발생을 Observer들에게 알리기 위해 notifyObservers 메서드가 있다.
Observer 인터페이스에는 Update 메서드가 있는데, 현재 메서드 인자는 Subject object를 전달하는 것이 아닌 값들을 넣어서 전달한다.
Subject의 구현체 WeatherData 클래스
import java.util.ArrayList;
public class WeatherData implements Subject {
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 0) {
observers.remove(i);
}
}
@Override
public void notifyObservers() {
for(int i = 0; i < observers.size(); i++) {
Observer observer = observers.get(i);
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();
}
}
Subject 구현체에서는 Observer를 멤버 변수로 관리한다. 그리고 registerObserver에서 인자로 받아온 Observer를 추가하고 반대로 removeObserver에서는 Observer를 제거한다.
setMeasurements() -> measurementsChanged() -> notifyObservers() 순서로 변경 시마다 모든 Observer의 update를 실행한다.
여기서 주목해야 할 점은 Observer 인터페이스를 Composition 하고 있기 때문에, Observer 인터페이스를 구현한 구현체는 모두 해당 Subject에 등록할 수 있다는 점이다. 즉, 새로운 Observer가 추가되어도 Subject에는 변경 사항이 없다.
Display Interface
public interface DisplayElement {
public void display();
}
Observer 구현체 1
public class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public StatisticsDisplay(Subject weatherData) {
this.weatherData = weatherData;
this.weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and" + humidity + "% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
Observer 구현체 2
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and" + humidity + "% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
Observer 패턴을 사용하는 Client (Main)
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.removeObserver(currentConditionDisplay);
weatherData.setMeasurements(20, 30, 11.2f);
weatherData.registerObserver(currentConditionDisplay);
weatherData.setMeasurements(10, 20, 5.8f);
}
}
자바 내장 옵저버 패턴 사용하기
java.util 패키지에 Observable, Observer를 이용해서 이를 구현할 수 있다.
위에 예제를 변경해 보겠다.
Observable 구현체
import java.util.ArrayList;
import java.util.Observable;
public class WeatherData extends Observable {
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
public void measurementsChanged() {
setChanged();
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;
}
}
바뀐 점은 이전에 있던, registerObserver, removeObserver, notifyObservers 메서드 정의가 없어진 점이다.
대신 measurementsChanged에서 Observable 클래스의 메서드인 setChanged()와 notifyObservers를 호출한다.
setChanged는 Observable 객체가 변경이 있는지 말 그대로 changed를 true로 변경하는 함수다.
//Observable 코드
protected synchronized void setChanged() {
changed = true;
}
notifyObservers는 notifyObservers()와 notifyObservers(Object arg) 메서드가 있는데, notifyObservers()를 호출하면 결국 내부에서 notfiyObservers(null)을 호출한다. (Object arg를 통해서 Observer들에게 보내고 싶은 데이터를 객체로 보낼 수 있다. 위에 예제에서는 데이터를 주고받기 위한 객체를 별도로 할당하지 않고 Subject에서 데이터를 가져와서 사용했다.)
//간략하게 변경한 Observable의 notifyObservers 메서드
public void notifyObservers(Object arg) {
if (!changed) return;
clearChanged();
for (Observer observer : Observers) {
observer.update(this, arg);
}
}
notfiyObservers(Object arg) 메서드에서는 changed 가 ture인지 보고 Observer들의 update 메서드를 호출해준다. (실제로는 changed를 확인하는 과정에서 thread safe 하도록 작동하기 위해 synchronized를 추가해주고, Vector로 선언되어 있는 Observer 객체들을 Object 배열로 변경해서 update메서드를 호출하고 있다.)
Observer 구현체
import java.util.Observable;
import java.util.Observer;
public class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Observable weatherData;
public StatisticsDisplay(Observable weatherData) {
this.weatherData = weatherData;
this.weatherData.addObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and" + humidity + "% humidity");
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData weatherObj = (WeatherData) o;
this.temperature = weatherObj.getTemperature();
this.humidity = weatherObj.getHumidity();
display();
}
}
}
Observer 인터페이스에 정의된 update 메서드를 구현해야 하기 때문에 코드가 변경된다.
Deprecated 된 Observable, Observer
Observer 패턴을 사용하려 하면 Deprecated 되었다고 나온다. (java 9 버전부터 Deprecated 되었다.) java 문서를 보면, Observer, Observable에서 지원하는 모델은 제한적이며, Observable에서 실행되는 알림이 전달될 순서가 지정되지 않으며 (무조건 등록된 순서대로 전달), 상태 변경이 notification과 일대일로 대응되지 않을 수 있다고 한다.
그러므로 java.beans 나 java.util.concurrent, Flow API를 사용하라고 얘기하고 있다.
이 부분은 나중에 한번 살펴봐야 할 것 같다.
@Deprecated(since="9")
public classObservable
extendsObject
참고문헌
- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠 / 서환수 역 / Head First Design Patterns / 한빛미디어 / 초판 19쇄 발행 2021년 1월 11일
- Java 9 Class Observable
'SW 설계 > Design Pattern' 카테고리의 다른 글
| [Design Pattern] 전략 패턴(Strategy Pattern) (1) | 2021.06.06 |
|---|---|
| [Design Pattern] 디자인 패턴이란? (0) | 2021.06.05 |
- Total
- Today
- Yesterday
- 웹 애플리케이션
- Spring Cloud Stream
- ExpectedException
- MySQL 외부 IP
- minikube node add
- Java 란
- Prometheus Operator
- 애플리케이션 변화 과정
- Java 특징
- WEB-INF
- 서버 클라이언트
- Kafka
- StreamBridge
- docker-compose
- Servlet
- springboot3.x
- Java 장단점
- 데스크톱 애플리케이션
- 애노테이션 프로세서
- producer
- 특정 ip
- OneToOne
- ServiceMonitor
- node add
- consumer
- cpus
- kubernetes
- DD파일
- minikube
- Servlet Container
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
