이 내용은 Head First 의 옵저버 패턴 챕터를 읽고 작성한 글 입니다.
WeatherData 클래스를 열어봅시다.
요구사항
class WeatherData {
getTemperature();
getHumidity();
getPressure();
measurementsChanged();
}
Java
복사
1.
WeatherData 클래스에 온도, 습도, 기압을 알아내기 위한 getter 메소드가 있다.
2.
새로운 측정 데이터가 나올 때 마다 measurementsChaged() 메소드가 호출된다.
3.
기상 데이터를 사용하는 3개의 display 항목을 구현해야 한다.
4.
시스템 확장이 가능해야 한다. 다른 개발자들이 별도의 display 항목을 만들 수 있도록 해야 하고 사용자들이 애플리케이션에 마음대로 항목을 추가/제거 할 수 있도록 해야한다.
옵저버 패턴 → 출판사(subject) + 구독자(observer)
옵저버 패턴의 정의
정의: 한 object 의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 갱신되는 방식으로 1:N 의존성을 정의한다.
•
subject 는 상태를 가지고 있다.
•
observer 는 의존성을 갖는다.
◦
단 subject 가 갱신해주기를 기다려야 한다.
신문사가 신문을 발행하고 구독자가 그 신문을 구독하는 프로세스와 비슷하다 → 구독, 구독 취소, 발행 등등
느슨한 결합(Loose coupling 의 위력)
→ 느슨한 결합: 상호작용은 하는데 서로 잘 모르는 사이이다.
•
subejct 가 가진 정보는 observer interface 를 구현한 것 뿐이다.
•
Observer interface 를 구현만 하면 되니 추가, 교체, 동적 변경이 가능하다 (SOLID 의 OCP 원칙)
•
observer 가 변경된다고 해서 subject 가 바뀔 필요가 없다.
•
독립적인 재사용 → 다른 용도로 써도 된다.
•
서로 interface 를 알기 때문에 나머지가 바뀌어도 문제 없다.
디자인의 원칙
서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
기상 스테이션의 설계
기상 스테이션의 구현
자바에서 observer 패턴의 일부를 지원하지만 많은 경우에 직접 구현하는 것이 더 낫다.
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
Java
복사
/**
* 모든 observer class 는 이 interface 를 구현해야 한다.
*/
public interface Observer {
void update(float temp, float humidity, float pressure);
}
Java
복사
public interface DisplayElement {
void display();
}
Java
복사
WeatherData 에서 subject interface 구현
public class WeatherData implements Subject {
public WeatherData() {
observers = new ArrayList<>();
}
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int index = observers.indexOf(o);
if (index >= 0) {
observers.remove(o);
}
}
/**
* 상태를 모든 observer 들에게 알려주는 역할을 한다.
*/
@Override
public void notifyObservers() {
observers.stream()
.forEach(observer -> observer.update(temperature, humidity, pressure));
}
public void measurementsChanged() {
notifyObservers();
}
/**
* 데이터를 받는곳, 데이터를 받으면 상태를 변경하고 모든 observer 에게 알려준다
* @param temp
* @param humidity
* @param pressure
*/
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
Java
복사
스프링에 사용한다면 bean 에 적합하지 않을까 싶기도 함.
디스플레이 항목
현재 기상 조건을 표시하는 display
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionDisplay(Subject subject) {
this.weatherData = subject;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("Current condition: " +
temperature + "F degrees and " + humidity + "% humidity");
}
}
Java
복사
@Test
@DisplayName("만들어둔 subject, observer 의 디스플레이 장치가 잘 동작하는지 확인해보자")
void printWeatherData() {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
Java
복사
1.
observer 와 subject는 서로에 대해 잘 모른다
2.
갱신이 발생하면 subject 는 observer 에게 알려주고, observer 는 subject에 변경이 발생함을 인식한다.
3.
갱신이 발생할 때 observer 는 갱신에 알맞은 로직을 수행한다.
4.
subject 는 자신의 상태(state)를 observer 에게 알려주지 않는다
java 가 제공하는 Observer
java9 부터 deprecated 되었으니 간단히 알아본다.
Observable 확인
Observer 확인
•
Observable : subject 역할을 수행한다
•
Observer: 옵저버 역할을 수행한다.
subject 의 가장 큰 차이점은 직접 구현했던 registerObserver(Observer o) 는 addObserver,
removeObserver(Observer o) 는 deleteObserver 로 변경되고 setChange 라는 함수를 통해 subject 에 갱신을 일으켜야 한다.
observer 는 비슷하게 update 를 사용한다.
update(Observable o, Object arg);
Java
복사
주의할 점
•
Observable 에서 observer 로 보내는 알림은 순서가 보장되지 않는다.
◦
하지만 순서가 보장된다면 느슨한 결합이라 할 수 없다.
•
Observable 은 클래스 이다.
◦
서브 클래스를 만들어야 한다 (오브젝트에서 이 문제점은 다루었으니 생략한다)
◦
인터페이스가 없기 때문에 Observer API 와 잘 맞는 클래스를 구현하기 불가능하다
◦
java.util 의 구현을 다른 구현으로 바꾸는 것이 불가능하기 때문에 multi thread 로 구현하는 것은 불가능하다.
•
클래스의 핵심 메소드를 외부에서 호출할 수 없다.
◦
setChanged() 는 protected 로 선언되어 있다.
◦
서브 클래스를 인스턴스 변수로 사용할 수 없게 된다(그냥 상속을 사용해야함)
물론 위의 문제는 Observable 을 직접 구현해서 필요한 함수들을 overriding 한다면 해결이 된다.
중요한 것은 Observer pattern 이 무엇이고 어떻게 써야하는지를 명확히 이해했다면 그 패턴을 사용한 API 를 얼마든지 구현할 수 있다.