[디자인 패턴] 옵저버 패턴(Observer Pattern) By starseat 2023-08-05 17:07:03 알고리즘 Post Tags # 옵저버 패턴 - 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식 - 일대다(ont-to-many) 의존성을 정의 - **(신문) 구독 서비스** 를 생각 - 신문사 = 주제, Subject - 구독자 = Observer - 자유롭게 서비스(이벤트)를 구독하고 탈퇴(해제) 가능 # 옵저버 패턴 구조 ![image.png](/uploads/_temp/20230805/4641a76ce9d25325a1550bde179e92de.png) - **Subject** - 주제를 나타내는 인터페이스 - 객체에서 옵저버로 등록하거나 옵저버 목록에서 탈퇴하고 싶을때 사용 - **ConcreteSubject** - Subject 인터페이스 구현 - 등록 및 해지용 메소드와 상태 변경시 모든 옵저버에게 연락하는 notifyObservers() 구현 - **Observer** - 옵저버 인터페이스 - 주제의 상태가 바뀌었을 때 호출되는 update() 선언 - **ConcreteObserver** - Observer 인터페이스 구현 - 각 옵저버는 특정 주제에 등록해서 연락 받을 수 있음. # 옵저버 패턴 적용 예제 - 기상 모니터링 - **Weather-R-Rama** - 온도, 습도, 기압 등의 정보를 제공 - *가상 스테이션* 이라는 명칭으로 예제에서 사용 - **WeatherData** (Subject) - 기상정보 정보 통합, 갱신 - **WeatherDisplay** (Observer) - 최신의 기상정보 표시 ## 인터페이스 - Subject ```java public interface Subject { public void registerObserver(Observers o); public void removeObserver(Observers o); public void notifyObservers(); // 주제 상태 변경 시 내용 알릴때 호출 되는 메소드 } ``` - Observer ```java public interface Observer { public void update(float temp, float humidity, float pressure); } ``` - Display ```java public interface DisplayElement { public void display(); } ``` ## 주제 인터페이스 구현 이 예제 중 주제 인터페이스의 구현체인 WeatherData 클래스 구현 ```java public class WeatherData implements Subject { private List observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } // 옵저버 등록 요청시 추가 public void registerObserver(Observers o) { observers.add(o); } // 옵저버 탈퇴 요청시 제거 public void removeObserver(Observers o) { observers.remove(o); } // 모든 옵저버에게 상태 변화 알림 // 모두 Observer 인터페이스를 상속받아 update() 가 있는 객체들 이어야됨. public void notifyObservers() { for(Observer ob : observers) { ob.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(); } // ... } ``` ## 옵저버 구현하기 이 예제 중 주제 옵저버의 구현체인 CurrentConditionsDisplay 클래스 구현 ```java // WeatherData 객체로부터 변경 사항을 받기 위해 Observer 구현 public class CurrentConditionsDisplay implements Observer, DisplayElement { private float temperature; private float humidity; private float pressure; private WeatherData weatherData; public CurrentConditionsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); // 옵저버 등록 } // Observer 의 update() 구현 // 기상 정보 저장 및 display() 호출 public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); } public void display() { System.out.println("===== 현재 상태 ====="); System.out.println("온도: " + temperature); System.out.println("습도: " + humidity + " %"); System.out.println("기압: " + pressure); System.out.println("===================="); } } ``` ## 실행 테스트 - 가상 스테이션 **Weather-R-Rama(가상 스테이션)** 실행 테스트 ```java public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(weatherData); weatherData.setMeasurements(24, 65, 30.4f); weatherData.setMeasurements(32, 70, 29.2f); } } ``` # 디자인 원칙 디자인 원칙 1~3 은 [전략 패턴(Strategy Pattern)](https://starseat.net/blog/view/189) 에 정리 ## 디자인 원칙 4 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야 한다. - **느슨한 결합(Loose Coupling)** - 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계 - 느슨한 결합을 사용하면 유연성이 좋아짐. - 옵저버 패턴이 느슨한 결합을 보여주는 훌륭한 예임. # 추가 ## java 의 Observable 클래스 java 에는 옵저버 패턴용 `Observable` 클래스(주제 클래스)와 `Observer` 인터페이스가 있었다. `Observable` 클래스는 직접 코드를 작성하지 않아도 옵저버를 추가하고 삭제하고 알림을 보내는 메소드를 제공했었다. 개발자들이 각자 자신의 코드에서 기본적인 옵저버 패턴을 지원하는게 더 편하다고 생각하는 사람들이 늘어나면서 **java 9 이후로는 사용하지 않는다.** ## 풀(pull) 방식으로 변경 위 예제는 **푸시(push, 주제가 옵저버로 데이터 전송)** 방식으로 되어 있으나 **풀(pull, 주제로부터 데이터를 당겨오는)** 방법이 더 좋음. ### 변경 1 WeatherData 의 notifyObservers() 수정 ```java public void notifyObservers() { for(Observer ob : observers) { // ob.update(temperature, humidity, pressure); // 파라미터 제거 ob.update(); } } ``` ### 변경 2 Observer 인터페이스의 update() 변경 ```java public interface Observer { // public void update(float temp, float humidity, float pressure); public void update(); } ``` ### 변경 3 옵저버 구현체인 CurrentConditionsDisplay 클래스의 update() 변경 ```java public void update() { //this.temperature = temperature; //this.humidity = humidity; //this.pressure = pressure; // 값 대입이 아닌 weatherData 의 getter 사용 this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } ``` # 참조 - [헤드 퍼스트 디자인 패턴](https://product.kyobobook.co.kr/detail/S000001810483) Previous Post [디자인 패턴] 전략 패턴(Strategy Pattern) Next Post [디자인 패턴] 데코레이터 패턴(Decorator Pattern)