Observer Pattern
Observer pattern allows a number of observer objects
to synchronize its state with the subject's state.
Observers(구독자, 가입자, 관찰자) 에게 특정 주제(subject)를 Push 하여 전달하거나
Pull 해서 Observer 를 필요시 주제를 가져온다.
옵저버 패턴을 트위터에 비유하여 표현한 사진을 보니 쉽게 이해가 되는 것 같다.
2개의 객체가 loosely coupled 되어있을때 , 두 객체는 상호작용한다. but 그것들은 서로를 모른다.
옵저버 패턴은 subject와 loosely coupled 된 옵저버들의 객체 설계 제공한다.
왜 그럴까?
오직 한 주제가 특정 인터페이스를 구현한 옵저버에 대해 알고있다.
옵저버의 concrete class가 그것이 무엇을 하는지, 또는 그것이 어떤 것이든지 알 필요가 없다.
언제든 새로운 옵저버 추가할 수 있다. 왜냐하면 오직 하나의 주제는 옵저버 인터페이스를 구현한 객체 리스트에 의존한다. 특정 옵저버를 런타임시 다른 옵저버로 대체할 수 있다. 마찬가지로 제거도 가능하다.
새로운 옵저버를 추가하기 위해 주제를 수정할 필요가 없다. 옵저버가 될 필요가 있는 새로운 concrete class를 가진다.
새로운 클래스 타입을 수용하기 위해 주제를 바꿀 필요가 없다.
새로운 옵저버로 등록하거나 우리는 새로운 클래스에 Observer 인터페이스를 구현해야 한다.
주제는 옵저버 인터페이스를 통해 구현한 객체에 전달될 것이다.
주제들과 옵저버들은 서로 독립적이기에 재사용할 수 있다.
주제 or 옵저버의 변경은 looesly coupled 이기에 서로 영향을 주지 않는다.
주제 인터페이스, 옵저버 인터페이스의 구현으로 되어있다면 각각은 변경에 자유롭다.
Loosely coupled designs 는 객체간 상호의존성이 최소화하기 때문에 유연하게 시스템을 변경할 수 있다.
- Head First Design Patterns -
적용할 설계 원칙
- 상호작용하는 객체간은 loosely coupled (서로를 모르게 함):
- Depends on abstraction / interface (not concretion).
- Polymorphic composition
Subject , Observer 주제와 관찰자는 유일하지않기 때문에 변경가능성이 크다.
특정 객체에 대해 Composition을 할 경우 Depends on Concretion, Tightly Coupled 이 발생할 수 있다.
∴ Polymorphic Composition 을 통해 객체간 Loose Coupling
정보 전송 방법
Push Model vs Pull model
Push Model Subject : 변경 정보를 옵저버에게 전달 |
Pull Model Subject : 변경 사실만 통지. Observer : 필요시 변경 정보 가져옴. |
|
Subject | void notifyObservers() { for (Observer ob : obList) ob.update( info ); } |
void notifyObservers() { for(Observer ob : obList) ob.update(); // 2개의 단계를 거친다. } Object getInfo() { return info; } |
Observer | void update(Object info){ this.info = info; } |
void update() { // get info from the subject // 조건 추가 가능 this.info = subject.getInfo(); } |
단점 | Observer 가 정보를 사용하지 않는 경우 비효율적 Observer 의 유형별로 서로 다른 정보를 전송해야할 경우 양자 간 커플링이 심화됨 |
두 단계로 이루어지므로 비효율적 또한 멀티 쓰레딩 환경에서 문제가 될 수 있다. |
ConcreteSubject 와 ConcreteObserver는 여러개 추가 생성 가능하다.
subject 변경 가능하게 하기 위해 객체간 1 to n 독립으로 정의돼있다.
모든 주제에 대한 Observers 는 자동으로전달되고 업데이트 된다.
--> 객체간 데이터 일관성 유지
Example
The Weather Station 예제
Push 모델
주제 , 관찰자 인터페이스 정의
package observer.weather;
public interface Subject
{
public void registerObsever(Observer observer); // 구독자 추가
public void removeObserver(Observer observer);
public void notifyObserver();
}
package observer.weather;
public interface Observer
{
// push model
public void update(float temp, float humidity, float pressure);
public void remove();
}
푸시 모델 update ( data.. )
Display(행동)에 대한 인터페이스 정의
package observer.weather;
public interface DisplayElement {
public void display();
}
Subject 구현
정보, 데이터..
package observer.weather;
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject
{
float temp, humidity, pressure;
// WeatherData 에 관심있어하는 구독자들 모음
List<Observer> observerList;
public WeatherData() {
observerList = new ArrayList<Observer>();
}
// Subject 인터페이스 구현
@Override
public void registerObsever(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
// 다형성을 이용해 업데이트
@Override
public void notifyObserver() {
for(Observer observer : observerList) observer.update(temp,humidity,pressure);
}
// 통지 방법에 융통성 부여
// 통지 방법 추상화
public void mesurementsChanged(){
notifyObserver();
}
// 기상청이 호출해서 수치 업데이트
public void setMeasurements(float temp, float humidity, float pressure)
{
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
mesurementsChanged();
}
}
옵저버 행동 구현
package observer.weather;
// 현재 조건
public class CurrentConditionsDisplay implements Observer, DisplayElement
{
// 관심 정보
private float temperature, humidity, pressure;
private Subject weatherData; // 탈퇴시 필요함.
public CurrentConditionsDisplay(Subject weatherData)
{
this.weatherData = weatherData;
// subject에 등록, 구독자 추가
weatherData.registerObsever(this);
}
// Observer
// PUSH model.
@Override
public void update(float temp, float humidity, float pressure)
{
temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void remove() {
weatherData.removeObserver(this);
}
// DisplayElement
@Override
public void display()
{
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity, "+
pressure + " P");
}
}
WeatherStation
메인 적용
package observer.weather;
//- 날씨 데이터를 수집하고,
//- 주기적으로 이를 WeatherData 객체에 씀.
public class WeatherStation {
public static void main(String[] args) {
// create subject
WeatherData weatherData = new WeatherData();
// create Observers
CurrentConditionsDisplay currentConDisplay = new CurrentConditionsDisplay(weatherData);
// Simulation
weatherData.setMeasurements(80,35,30.4f);
}
}
날씨 데이터를 set 설정하면
// 기상청이 호출해서 수치 업데이트
public void setMeasurements(float temp, float humidity, float pressure)
{
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
mesurementsChanged();
}
// 통지 방법에 융통성 부여
// 통지 방법 추상화
public void mesurementsChanged(){
notifyObserver();
}
// 다형성을 이용해 업데이트
@Override
public void notifyObserver() {
for(Observer observer : observerList) observer.update(temp,humidity,pressure);
}
update 에서 PUSH 방식으로 display() 메소드를 실행하여 출력
// Observer
// PUSH model.
@Override
public void update(float temp, float humidity, float pressure)
{
temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
notifyObserver에 다형성에 의해 날씨 정보가 모든 observer 들에게 전달된다.
PULL 모델
Observer 인터페이스에 메소드 등록
public interface Observer
{
// push model
public void update(float temp, float humidity, float pressure, float windSpeed);
// pull model
public void update(Subject subject);
public void remove();
}
public class WeatherData implements Subject
{
private float temp, humidity, pressure;
private float windSpeed;
// WeatherData 에 관심있어하는 구독자들 모음
List<Observer> observerList;
public WeatherData() {
observerList = new ArrayList<Observer>();
}
...
// 다형성을 이용해 업데이트
@Override
public void notifyObserver() {
// update()
for(Observer observer : observerList) observer.update(this); // pull model
}
...
getter
}
update가 호출되면
// 현재 조건
public class CurrentConditionsDisplay implements Observer, DisplayElement
{
....
// PULL model.
@Override
public void update(Subject subject)
{
if(subject instanceof WeatherData)
{
WeatherData weatherData = (WeatherData)subject;
this.temperature = weatherData.getTemp(); // pulling
this.windSpeed = weatherData.getWindSpeed(); // pulling
display();
}
}
...
}
pulling 방식으로
get 메소드에 의해 받아 들인다.
이 과정에서 update 와 getter 두 단계로 이루어지므로 비효율적이며
또한 멀티 쓰레딩 환경에서 문제가 될 수 있다.
책 참고 : Head First Design Patterns
댓글