오늘은 행위패턴 중 하나인 옵저버패턴에 대해 알아보겠다.

옵저버 패턴은 신문사와 구독자와의 상호작용이라고 이해하면 된다.
신문사에 구독한 구독자는 신문이 발행될때마다 신문사로부터 새로운 신문을 받을 수 있습니다.
옵저버 패턴은 한 객체의 상태가 바꾸면 그 객체에 의존하는 다른 객체에게 연락이 가고
자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의합니다.
구체적인 예시로 실시간 기상청 서비스를 제공해야하는 요구사항이 생겼습니다.
온도와 습도를 입력받으면 온도와 습도를 출력해주는 여러 디스플레이를 개발해야한다.
요구사항
-> 새로운 온도, 습도 등 데이터를 입력받으면 그 데이터를 활용하는 디스플레이들을 업데이트시켜 출력해야한다.
안좋은 예시
요구사항대로 날씨 정보를 입력받으면 관련 디스플레이들이 업데이트된 정보에 따라 출력을 한다.
하지만 왜 안좋은 예시일까?
public class WeatherData {
private CurrentConditionDisplay currentConditionDisplay;
private StatisticsDisplay statisticsDisplay;
public WeatherData(CurrentConditionDisplay currentConditionDisplay, StatisticsDisplay statisticsDisplay) {
this.currentConditionDisplay = currentConditionDisplay;
this.statisticsDisplay = statisticsDisplay;
}
public void measurementsChanged(float temp, float humidity) {
currentConditionDisplay.update(temp, humidity); // 새로운 디스플레이가 추가되거나 제거될때 이 코드를 수정해야함
statisticsDisplay.update(temp, humidity); // 어떤 디스플레이인지, 어떤 행위를 하는지 알아야함
}
}
public class CurrentConditionDisplay {
private float temp;
private float humidity;
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("현재 온도는 " + temp);
System.out.println("현재 습도는 " + humidity);
}
}
public class StatisticsDisplay {
private float temp;
private float humidity;
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("통계 display 입니다 !" + temp + humidity);
}
}
public class Run {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(new CurrentConditionDisplay(), new StatisticsDisplay());
weatherData.measurementsChanged(1, 2333);
System.out.println("-------------------");
weatherData.measurementsChanged(10, 22);
System.out.println("-------------------");
weatherData.measurementsChanged(11, 23);
}
}

정리하면 추가되거나 제거해야하는 디스플레이를 관리하기 어렵습니다. 변화된 디스플레이에 대해 코드를 수정해야하는 등 민감하게 반응해야하기 때문입니다.
좋은예시
public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void noticeObservers();
}
주제 인터페이스
옵저버 인터페이스를 구현한 객체를 등록, 제거 그리고 정보가 업데이트되면 객체들에게 알리는 메소드를 가집니다.
public interface Observer {
void update(float temp, float humidity);
}
옵저버 인터페이스
업데이트 등 원하는 기능의 메소드를 약속한다.
public class WeatherData implements Subject {
private List<Observer> observerList = new ArrayList<>();
private float temp;
private float humidity;
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void noticeObservers() {
for (Observer observer : observerList) {
observer.update(temp, humidity);
}
}
public void setMeasurements(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
noticeObservers();
}
}
주제 인터페이스를 구현한 날씨데이터 객체 (주제)
옵저버 객체들을 추가하거나 삭제하여 관리할 수 있고 값이 변경될때 notice할 수 있다.
public class CurrentConditionDisplay implements Observer{
private float temp;
private float humidity;
private WeatherData weatherData;
public CurrentConditionDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("현재 온도는 " + temp);
System.out.println("현재 습도는 " + humidity);
}
}
public class StatisticsDisplay implements Observer {
private float temp;
private float humidity;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("통계 display 입니다 !" + temp + humidity);
}
}
옵저버 인터페이스를 구현한 디스플레이(옵저버)
약속된 update() 메소드를 디스플레이들에 맞게 구현한다.
디스플레이가 weatherData(주제)를 내부로 들고 있으면 주제 -> 옵저버에게 정보를 전달하는 방법말고
옵저버가 주제로부터 데이터를 받아올 수 있다.(풀 방식)
매개변수를 없애고 weatherData.getTemp()로 업데이트를 진행한다. 원하는 데이터만 가져올 수 있다.
public class Run {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(11, 15);
System.out.println("-------------------");
weatherData.setMeasurements(22, 60);
System.out.println("-------------------");
weatherData.removeObserver(statisticsDisplay);
weatherData.setMeasurements(28, 50);
}
}

옵저버 패턴을 사용하여 새로운 디스플레이를 확장하는데 유연하고 원하는 시점에 디스플레이를 추가하고 제거할 수 있도록 하였습니다.
인터페이스를 통해 느슨한 결합을 만들었습니다. 주제는 옵저버를 구현한 구상 클래스가 무엇인지 무엇을 하는지 알필요가 없습니다. 새로 옵저버를 구현한 구상 클래스를 쉽게 추가하고 제거할 수 있습니다.
장점
- 인터페이스를 통한 느슨한 결합으로 유연하게 객체 사이 상호작용을 할 수 있음 -> 확장성, 유지보수
단점
- 여기저기 많이 사용하게 되면 등록, 제거 부분이 복잡해짐
- 동시성 문제도 있음 -> 제거되어야 할 옵저버에 update, 새로 생긴 옵저버에 update 되지 않음
'리팩토링 > 디자인패턴' 카테고리의 다른 글
| [디자인패턴] 퍼사드 패턴 (1) | 2022.11.26 |
|---|---|
| [디자인패턴] 어댑터 패턴 (1) | 2022.11.14 |
| [디자인패턴] 팩토리 메서드 패턴, 추상 팩토리 패턴 (1) | 2022.10.30 |
| [디자인패턴] 싱글턴 패턴 (2) | 2022.10.24 |
| [디자인패턴] 전략 패턴 (6) | 2022.10.10 |