2일차, 옵저버 패턴을 공부했다!
옵저버 패턴은 느슨한 결합을 사용하는 패턴이다. 어떠한 객체의 변경이 이루어졌을때, 그 객체에 의존하는 다른 객체들에게 어떠한 신호가 가고, 자동으로 내용이 업데이트 되는 일대다 의존성에 사용된다.
만약 Weather 클래스가 있고, 그 Weather 클래스에는 기상 상황을 나타내는 여러 변수가 있다. 외부로부터(우리는 Main메서드에서) 값의 변경이 이루어졌을때, ForecastDisply, TemperatureDisplay등의 클래스에서 자동으로 변경 사항을 출력하고자 한다.(이 디스플레이들은 언제든지 추가/변경/삭제 될 수 있다.) 어떻게 구현하는게 좋을까?
일단 Weather가 업데이트 되었을 때, 각 디스플레이를 호출할 공통된 메서드-update()가 필요할 것 같다. 그렇게 만들면, update() 메서드를 가진 Observer 인터페이스로 디스플레이들을 추상화한 후, iterator로 이용해서 update를 뿌릴 수 있기 때문이다. 구현해보면 이 정도 코드가 될 것 같다.
public class Weather implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(float temperature, float humidity, float pressure);
}
}
//외부로부터 호출되는 메서드
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObserver();
}
}
이렇게 만들면 좋을 것 같다. 외부로부터 setMeasurements() 메서드가 호출되면 값을 업데이트하고 Observer들에게 신호를 보낸다.(update) 이러면 디스플레이에서 신호를 받아 자신의 값을 또한 업데이트하고 출력하면 된다.
@Slf4j
public class ForecastDisplay implements Observer, DisplayElement {
//Observer는 update메서드를 추상화, DisplayElement는 display메서드를 추상화하였다.
private float temperature;
private float humidity;
private float pressure;
public ForecastDisplay(Weather weather) {
weather.addObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
log.info("기상 디스플레이 - 온도 : {} / 습도 : {} / 기압 : {}", temperature, humidity, pressure);
}
}
이렇게 Display클래스도 짜면 된다. 좋다! 하지만 문제점이 하나가 있다. 현재는 ForecastDisplay 클래스 하나만 존재하지만 미래에 다른 Observer들이 추가될 수 있다. 그렇다면 그 클래스는 어쩌면 3가지 정보 중 일부만 필요할 수도 있다. 그런 클래스은 update 방식을 또 다르게 구성해야 한다. 그런 수고를 줄일 수는 없을까?
풀 방식으로 업데이트를 하면 위 문제를 해결할 수 있다. Weather클래스에서 Observer로 정보를 밀어주는 것이 아니라, Weather클래스는 Observer에게 신호만 보내고, 정보를 업데이트하는 것은 Observer에게 맡기는 것이다. 이렇게 하면 각 Observer마다 필요한 정보만 취할 수 있으므로 후에 추가/수정에 용이하다. 바뀐 코드는 이렇게 된다.
@Getter
public class Weather implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObserver() {
for (Observer observer : observers) {
//신호만 준다!
observer.update();
}
}
//외부로부터 호출되는 메서드
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObserver();
}
}
@Slf4j
public class ForecastDisplay implements Observer, DisplayElement {
//Observer는 update메서드를 추상화, DisplayElement는 display메서드를 추상화하였다.
private float temperature;
private float humidity;
private float pressure;
//이제는 Weather를 멤버로 갖는다!!!
private final Weather weather;
public ForecastDisplay(Weather weather) {
this.weather = weather;
weather.addObserver(this);
}
@Override
public void update() {
//Weather의 Getter메서드로 풀 한다!!!
this.temperature = weather.getTemperature();
this.humidity = weather.getHumidity();
this.pressure = weather.getPressure();
display();
}
@Override
public void display() {
log.info("기상 디스플레이 - 온도 : {} / 습도 : {} / 기압 : {}", temperature, humidity, pressure);
}
}
기상 디스플레이 추가
온도 변경1
기상 디스플레이 - 온도 : 10.0 / 습도 : 10.0 / 기압 : 10.0
온도 변경2
기상 디스플레이 - 온도 : 20.0 / 습도 : 30.0 / 기압 : 40.0
온도 디스플레이 추가
온도 변경3
기상 디스플레이 - 온도 : 40.0 / 습도 : 20.0 / 기압 : 30.0
온도 디스플레이 - 온도 : 40.0
이렇게 Weather를 구성하게 되면 후에 다른 Observer가 생기더라도 Weather클래스를 수정할 일이 없다. 이제 정리해보자.
옵저버 패턴은 일대다 의존성에 유용하다. 한 객체의 상태가 변했을 때, 다른 객체들에게 자동으로 신호가 가고 처리를 하는 형태의 코드는 사실 우리가 많이 접해왔다. 자바빈 등에서도 볼 수 있는 패턴이다.
옵저버 패턴은 한때 java.util에서 제공하기도 하였지만 기능이 너무 빈약하여 그냥 개발자가 직접 구현하는 것이 용이하다고 판단하였는지 deprecated되었다. 이제는 옵저버 패턴을 몸으로 익혀서 써야할 것 같다.
또한 옵저버 패턴을 통해 느슨한 결합에 대해 공부할 기회가 있었다. 느슨한 결합은 계속 반복되는 개념인데, 서로에게 강하게 의존하지 않아서 추가/변경/삭제가 쉽고 독립적으로 재사용될 수 있으며 영향을 미치지 않는 형태이다.
위 글은 다음 책을 바탕으로 작성하였습니다.
헤드퍼스트 디자인패턴 : https://www.yes24.com/Product/Goods/108192370
소스 코드 : https://github.com/Dompoo/DesignPatternStudy/tree/master/src/main/java/dompoo/study/observerPattern