FIF's 코딩팩토리

옵저버 패턴(Observer Pattern) 정리 본문

Back-End/Design Pattern(디자인 패턴)

옵저버 패턴(Observer Pattern) 정리

FIF 2019. 6. 5. 14:30
반응형

옵저버 패턴(OBSERVER PATTERN) 정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로

일대다(one-to-many) 의존성을 정의한다.

 

옵저버 패턴을 구현하는 방법에는 여러가지가 있지만, 대부분 상태를 저장하고 있는 주제 인터페이스를 구현한 하나의 주제객체와 주제객체에 의존하고 있는 옵저버 인터페이스를 구현한 여러 개여 옵저버객체가 있는 디자인을 바탕으로 한다.

 

데이터 전달방식 2가지

1.     주제객체에서 옵저버로 데이터를 보내는 방식(Push 방식)

2.     옵저버에서 주제객체의 데이터를 가져가는 방식(Pull 방식)

 

옵저버 패턴 클래스 다이어그램

 

디자인 원칙

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

 

1.     옵저버 패턴은 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공한다.

2.     주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스(Observer Interface)를 구현 한다는 것 뿐이다.

3.     옵저버는 언제든지 새로 추가할 수 있다.(주제는 Observer Interface를 구현하는 객체 목록에만 의존하기 때문이다.)

4.     새로운 형식의 옵저버를 추가하려 해도 주제를 전혀 변경할 필요가 없다.(새로운 클래스에서 Observer Interface만 구현해주면 되기 때문이다.)

5.     주제나 옵저버가 바뀌더라도, 서로에게 영향을 주지 않는다. 그래서 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.

 

느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있다.(객체 사이의 상호 의존성을 최소화 할 수 있기 때문이다.)

 

문제 발생

날씨 데이터를 가지고 있는 회사와 데이터를 연동하여 여러종류의 디스플레이에 날씨 데이터를 출력해줘야 하는 일이 생겼다고 가정하자.

 

제공받은 객체와 각 메소드의 역할

    

                  

getTemperature() : 온도

getHumidity() : 습도

getPressure() : 기압

measurementsChanged() : 새로운 기상 측정 데이터가 나올 때 마다 자동으로 호출되는 부분

 

대략적인 구상

measurementsChanged 메소드 안의 Display update 메소드들이 구체적인 구현에 맞춰 코딩이 되있기 때문에, 프로그램을 고치지 않고는 다른 디스플레이 항목을 추가/제거 할 수 없다.

향후에 바뀔 수 있는 부분은 캡슐화해서 분리하여야 한다.

모든 디스플레이에 효과적으로 Weather상태를 알려줄 수 있는 방법이 필요하다.

public class WeatherData{
     // 인스턴스 변수들
     public void measurementsChanged(){ //새로운 데이터 세팅시 갱신되는 메소드
         float temp = getTemperature();
         float humidity = getHumidity();
         float pressure = getPressure();
        //3개의 디스플레이가 있다고 가정
         currentCondirionsDisplay.update(temp, humidity, pressure); //디스플레이 갱신
         statisticsDisplay.update(temp, humidity, pressure); //디스플레이 갱신
         forecastDisplay.update(temp, humidity, pressure); //디스플레이 갱신
     }
     //기타 메소드…
 }

 

옵저버 패턴 구현하기

 

Subject.java

public interface Subject {
           public void registerObserver(Observer o);
          //위아래 메소드는 Observer를 인자로 받는다. 옵저버 등록/제거 역할
           public void removeObserver(Observer o);
           //주제 객체의 상태가 변경되었을 때 모든 옵저버들한테 알리기 위한 메소드
           public void notifyObservers();
      }

 

Oberser.java

public interface Observer {
           //기상 정보가 변경되었을때 옵저버 한테 전달되는 상태 값
           //이 인터페이스는 모든 옵저버 클래스에서 구현해야 한다.
           //모든 옵저버는 update()메소드를 구현해야 한다.
           public void update(float temp, float humidity,  float pressure);
     }

 

DisplayElement.java

public interface DisplayElement {
	//디스플레이 항목을 화면에 표시해야 하는 경우 호출하는 메소드
	public void display();
}

 

WeatherData.java

import java.util.ArrayList;
public class WeatherData implements Subject {
                       // Observer 객체들을 저장하기 위해 ArrayList추가
                       private ArrayList observers;
                       private float temperature;
                       private float humidity;
                       private float pressure;
                       public WeatherData() {
                                 // ArrayList형 observer객체 생성(생성자)
                                 observers = new ArrayList();
                       }
                       //옵저버가 등록하면 목록 맨 뒤에 추가만 하면 된다.
                       @Override
                       public void registerObserver(Observer o) {
                                 observers.add(o);
                       }
                      //중요! 상태에 대해서 모든 옵저버들한테 알려주는 메소드
//모두 Observer인터페이스를 구현하는, 즉 update()메소드가 있는 객체들이므로 손쉽게 알려줄 수 있다.
                       @Override
                       public void notifyObservers() {
                                 for (int i = 0; i < observers.size(); i++) {
                                             Observer observer = (Observer) observers.get(i);
                                             observer.update(temperature, humidity, pressure);
                                 }
                       }
                       //옵저버가 탈퇴를 신청하면 목록에서 뺀다.
                       @Override
                       public void removeObserver(Observer o) {
                                 int i = observers.indexOf(o);
                                 if (i >= 0) {
                                             observers.remove(i);
                                 }
                       }
                       //기상 스테이션으로 부터 갱신된 측정치를 받으면 옵저버들한테 알린다.
                       public void measurementsChanged() {
                                 notifyObservers();
                       }
                      public void setMeasurements(float temperature, float humidity, float pressure) {
                                 this.temperature = temperature;
                                 this.humidity = humidity;
                                 this.pressure = pressure;
                                 measurementsChanged();
                       }
}

 

CurrentConditions.java

public class CurrentConditionsDisplay implements Observer, DisplayElement {
                       private float temperature;
                       private float humidity;
                       private Subject weatherData;
                       public CurrentConditionsDisplay(Subject weatherData) {
                                 this.weatherData = weatherData;
                                 weatherData.registerObserver(this);
                       }
                       @Override
                       public void update(float temperature, float humidity, float pressure) {
                                 this.temperature = temperature;
                                 this.humidity = humidity;
                                 display();
                       }
                       //가장 최근에 얻은 기온과 습도 출력
                       @Override
                       public void display() {
                                 System.out.println("Current conditions: " + temperature + "F degree and" + humidity + "% humidity");
                       }
}

 

WeatherStation.java

public class WeatherStation {
                       public static void main(String[] args) {
    WeatherData weatherData = new WeatherData();
    CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
                        //새로운 기상 측정값이 들어온 것처럼 만든다.
    weatherData.setMeasurements(80, 65, 30.4f);
    weatherData.setMeasurements(82, 70, 29.2f);
    weatherData.setMeasurements(78, 92, 29.2f);
                       }
}

 

자바에 내장된 옵저버 패턴을 사용하여 구현하기

java.util.Observer 인터페이스와 java.util.Observable 클래스를 사용할 수 있다.

자바 내장 옵저버 패턴은 push, pull 방식 모두 사용 가능하다.

 

자바 내장 옵저버 패턴 클래스 다이어그램

 

이전에 구현 했던 것과 마찬가지로 java.util.Observer 인터페이스를 구현하고, java.util.Observable  객체의 addObserver()메소드를 호출하면 옵저버가 옵저버 목록에 추가되고 deleteObserver()를 호출하면 옵저버 목록에서 탈퇴된다.

 

연락을 돌리는 방법은 java.util.Observable를 상속받는 주제 클래스에서 setChanged() 메소드를 호출해서 객체의 상태가 바뀐걸 알린 후, notifyObservers() 또는 notifyObserver(Object args)메소드를 호출하면 된다.(인자값을 넣어주는 메소드는 push방식으로 쓰인다.)

 

옵저버 객체가 연락받는 방법은 update(Observable o, Object args) 메소드를 구현하면 된다.

Observable에는 연락을 보내는 주제 객체가 인자로 전달되고,

Object args 같은 경우는 notifyObservers(Object arg) 메소드에서 인자로 전달된 데이터 객체가 넘어온다.

 

WeatherData.java

public class WeatherData extends Observable{       
           private float temperature;
           private float humidity;
           private float pressure;
           public void measurementsChanged(){ 
					//상태가 바뀌었다는 플래그값을 바꿔줌.
                      this.setChanged();          
					//풀 방식을 사용해서 알림
                      this.notifyObservers();     
}
           //값이 세팅된다고 가정.
           public void setMeasurementsChanged(float t, float h, float p){          
                      this.temperature = t;
                      this.humidity = h;
                      this.pressure = p;
                      this.measurementsChanged();
           }
           public float getTemperature() {
                      return temperature;
           }
           public float getHumidity() {
                      return humidity;
           }
           public float getPressure() {
                      return pressure;
           }
 }

 

CurrentConditions.java

public class CurrentConditions implements Observer, DisplayElement{
           private Observable observable;
           private float temperature;
           private float humidity;
           public CurrentConditions(Observable observable) {
                      this.observable = observable;
                      this.observable.addObserver(this);
           }
           @Override
           public void display() {
                      System.out.println("Current conditions : "+temperature+" , "+humidity);
           }
           @Override
           public void update(Observable o, Object arg) {
                      if(o instanceof WeatherData){
                                 WeatherData weatherData = (WeatherData) o;
                                 this.temperature = weatherData.getTemperature();
                                 this.humidity = weatherData.getHumidity();
                                 this.display();
                      }
           }
 }

 

java.util.Observable의 단점

1.      Observable은 클래스이기 때문에, 서브클래스를 만들어야 한다. 이미 다른 수퍼클래스를 확장하고 있는 클래스에 Observable의 기능을 추가할 수 없어 재사용성에 제약이 생긴다.

 

2.     Observable 인터페이스라는 것이 없기 때문에, 자바에 내장된 Observer API하고 잘 맞는 클래스를 직접 구현하는 것이 불가능 하다.

 

 

3.     java.util.Observable을 확장한 클래스를 쓸 수 있는 상황이라면 Observer API를 쓰는것도 괜찮겠지만, 상황에 따라선 직접 구현해야 할 수도 있다.

 

결론 : 둘 중, 어떤 방법을 쓰든 옵저버 패턴만 제대로 알고 있다면 그 패턴을 활용하는 API는 무엇을 쓰든간에 잘 활용할 수 있을 것이다.

 

반응형
Comments