배경

날씨 데이터를 디스플레이 장비에 표시하는 애플리케이션 개발 요청입니다.

요구사항

  1. WeatherData 객체를 통해 온도, 습도, 기압 3가지 디스플레이 항목을 표시해야 합니다.
    1. WeatherData는 실시간으로 갱신됩니다.
  2. 데이터는 디스플레이 장비에 표시되어야 합니다.
  3. 디스플레이 항목은 개발자가 손쉽게 추가할 수 있어야 합니다.

패턴 미적용

public class WeatherData {
    private double temperature;
    private double humidity;
    private double pressure;
    
    public void measurementsChanged() {
        double temp = getTemperature();
        double humidity = getHumidity();
        double pressure = getPressure();
        
        CurrentConditionsDisplay ccd = new CurrentConditionsDisplay();
        ccd.update(temp, humidity, pressure);
        
        StatisticsDisplay sd = new StatisticsDisplay();
        sd.update(temp, humidity, pressure);
        
        ForecastDisplay fd = new ForecastDisplay();
        fd.update(temp, humidity, pressure);
    }
}

디자인 원칙으로 문제점 찾기

  • 변경되지 않는 부분과 변경되는 부분을 구분하고, 변경되는 부분을 찾아 캡슐화해야 합니다.
    • 요구사항에 따라 Display는 추가가 가능해야 하므로 Display의 개수는 변경될 예정입니다.
    • Display는 WeatherData의 업데이트된 데이터를 update()로 호출하여 반영하도록 공통됩니다.
      • 어떤 정보를 받아볼지는 Display마다 다를 수 있습니다.
    • Display는 WeatherData에서 받은 정보를 표시합니다.
  • 구현은 인터페이스에 맞춰 프로그래밍하는 것이 바람직합니다.
    • 현재는 CurrentConditionsDisplay와 같은 구체 클래스에 의존하고 있습니다.

리팩토링 계획

  • WeatherData 관련
    • 변경된 기상 정보를 제공할 Display 목록을 관리해야 합니다(추가, 삭제).
      • 기상 정보가 변경될 때마다 WeatherData에서 Display에 전달할 수 있어야 합니다(Displayupdate 호출이 필요합니다).
      • → 리스닝하는 Display를 관리하는 객체로 추상화합니다.
      • (추가) 추후 다른 데이터도 제공할 수 있도록, Display처럼 최신 정보를 지속적으로 제공해야 하는 객체들을 다루는 인터페이스로 추상화합니다.
  • Display 관련
    • Display들은 WeatherData에서 제공하는 변경된 정보를 반영하는 update() 메서드를 포함해야 합니다.
      • 어떤 정보를 받아볼지는 Display마다 다를 수도 있고 같을 수도 있습니다.
      • → 구현체 클래스에서 적절히 Override합니다.
    • Display들은 WeatherData에서 제공받은 정보를 표시하는 display() 메서드를 구현해야 합니다.
      • 어떤 정보를 표시할지는 Display마다 다를 수도 있고 같을 수도 있습니다.
      • → 구현체 클래스에서 적절히 Override합니다.
    • 즉, 변경된 정보를 반영하는 인터페이스와 어떤 정보를 표시하는지에 대한 두 개의 인터페이스가 필요합니다.

위와 같이 주기적으로 특정 정보를 받아봐야 하는 상황에 사용할 수 있는 패턴이 옵저버 패턴입니다.

옵저버 패턴의 정의

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

신문사와 구독자의 관계로 비유할 수 있습니다. 이때 신문사를 Subject, 구독자를 Observer라고 볼 수 있습니다.

옵저버 패턴을 아래의 디자인 원칙을 추가하여 적용해보겠습니다.

발행-구독 패턴(pub/sub pattern)과 같은 개념인지에 대한 질문 → 다릅니다

  • pub/sub은 1:N이 아니라 N:M 관계입니다.
  • Subject처럼 Observer를 직접 관리하지 않습니다.
  • 중간 관리자(메시지 브로커)가 존재합니다.
  • 서로 알 필요가 없어 더 유연합니다.
  • 대부분 비동기식(이벤트 큐, 메시지 큐 기반)으로 동작합니다.

디자인 원칙(느슨한 결합)

디자인 원칙
상호작용하는 객체 사이에는 가능하면 느슨한 결합(Loose coupling)을 사용해야 합니다.

  • 느슨한 결합은 객체들이 상호작용할 수는 있지만, 서로를 잘 모르는 관계를 의미합니다.
  • 느슨한 결합을 사용하면 유연성이 높아집니다.
    • Subject는 자신의 데이터를 받는 객체들이 Observer 인터페이스를 구현했다는 사실만 알고 있습니다.
    • 따라서 Observer가 아무리 많아져도 신경 쓸 필요가 없으므로 확장에 용이합니다.
    • Observer가 변경되어도 Subject를 수정할 필요가 없습니다.
    • Subject와 Observer는 서로 독립적으로 재사용 가능합니다.
    • Observer에서 update만 제대로 구현되어 있다면 어떻게 수정되어도 Subject에는 영향을 주지 않습니다.
    • Subject를 어떻게 수정하더라도 Observer의 update만 제대로 호출되면 됩니다.

디자인 패턴을 적용한 리팩토링

WeatherData 설계

  • Subject 인터페이스를 만들어 구현합니다.
public interface Subject {  
  void registerObserver(Observer observer);  
  void removeObserver(Observer observer);  
  void notifyObservers();  
}

Display들(Observer) 설계

  • Observer, DisplayElement 인터페이스를 생성하고 구현합니다.
public interface Observer {  
  void update(double temperature, double humidity, double pressure);  
}

public interface DisplayElement {  
  void display();  
}

자세한 코드는 Github 을 참고하시기 바랍니다.