본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 02. 옵저버 패턴(Observer Pattern)

by 디토20 2022. 6. 12.
반응형

 

 

 

[헤드퍼스트 디자인패턴] 02. 옵저버 패턴(Observer Pattern)

 

 

1. 옵저버 패턴이란?

 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다. 

 

 

2. 구현 목표

1. WeatherData 클래스에는 3가지 측정값(온도, 습도, 기압)의 게터 메소드가 있다.

2. 새로운 기상 측정 데이터가 들어올 때마다 measurementsChanged() 메소드가 호출된다.

3. 기상 데이터를 사용하는 3가지(현재 조건, 기상 통계, 기상 예보) 디스플레이를 구현한다. 

4. 디스플레이를 업데이트하도록  measurementsChanged() 메소드에 코드를 추가해야 한다.

 

 

3. 고려사항

 - 확장성 : 추후 디스플레이스가 늘어나거나 감소할 수 있다.

 

 

4. 구현

4.1 문제가 있는 코드

public class WeatherData {

   // 인스턴스 변수 선언

    public void measurementsChanged() {
         float temp = getTemperature();
         float humidity = getHumidity();
         float pressure = getPressure();

         currentConditionsDisplay.update(temp, humidity, pressure);
         statisticsDisplay.update(temp, humidity, pressure);
         forecastDisplay.update(temp, humidity, pressure);

      }

     // 기타 메소드

}

 

문제점
  • 구체적인 구현에 맞춰서 코딩했으므로 프로그램을 고치지 않고는 다른 디스플레이를 추가하거나 제거할 수 없다.
  • 새로운 디스플레이 항목이 추가될 때마다 코드를 변경해야 한다.
  • 인터페이스가 아닌 구체적인 구현을 바탕으로 코딩했다.
  • 바뀔수 있는 부분을 캡슐화하지 않았다.

 

 

 

4.2 옵저버 패턴을 활용한 구현

옵저버 패턴은 여러 가지 방법으로 구현할 수 있지만, 보통은 주제 인터페이스와 옵저버 인터페이스가 들어있는 클래스 디자인으로 구현한다.

 

 

 

 

4.2.1 코드 구현

// interface 구현

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

public interface Observer {
    public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
    public void display()
}
// subject class

public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<Observer>();
    }

    public void registerObserver(Observer o){
        observers.add(o);
    }
    
    public void removeObserver(Observer o) {
        observers.add(o);
    }
    
    public void notifyObservers() {
        for(Observer observer : observers) {
            observer.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();
    }
    
    // 기타 WeatherData 메소드
}
// 디스플레이 요소 구현

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private WeatherData weatherData;
    
    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    
    public void display() {
        System.out.println(" 현재 상태: 온도 " + temperature
        + "F, 습도 " + humidity + "%");
    }
}

 

 

 

5.Pull vs Push

 pull

pull 방식을 사용할 시, 추후 WeatherData에 풍속같은 새로운 데이터가 추가된다면, 대부분의 update()에서 풍속 데이터를 사용하지 않더라도 모든 디스플레이에 있는 update() 메소드를 변경해야 한다.

 

push

push 방식 사용 시, 옵저버가 필요한 데이터를 골라서 가져올 수 있다.

 

 

5.1 Pull -> push로 코드 변경

// WeatherData class
public void notifyObservers() {
        for(Observer observer : observers) {
           // observer.update(temperature, humidity, pressure); -> 삭제
           observer.update(); -> 추가
        }
    }
    

// Observer interface
public interface Observer {
    //public void update(float temp, float humidity, float pressure); -> 삭제
    public void update(); -> 추가
}


// 디스플레이 요소 구현
//public void update(float temperature, float humidity, float pressure) { -> 삭제
public void update() { -> 추가
//        this.temperature = temperature; -> 삭제
//        this.humidity = humidity; -> 삭제
        this.temperature = weatherData.getTemperature(); -> 추가
        this.humidity = weatherData.getHumidity; -> 추가
        display();
    }

 

 

 

6. 옵저버 패턴의 장점

1. 느슨한 결합(Loose Coupling) : 주제는 옵저버가 특정 인터페이스를 구현한다는 사실만 알 뿐, 옵저버의 구상 클래스가 무엇인지, 옵저버가 무엇을 하는지 알 필요가 없다.

 

2. 옵저버는 언제든지 새로 추가하거나 제거할 수 있다.

 

3. 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 없다.

 

4. 주제와 옵저버는 서로 독립적으로 재사용 할 수 있다.

 

5. 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.

 

 

 

 

 

 

디자인 원칙

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

 

 

728x90
반응형

댓글