소프트웨어 관련/Design Pattern

[Design Pattern] Observer Pattern

JJangGu 2022. 4. 6. 00:21

헤드퍼스트의 Design Patterns 중 두번째, Observer Pattern 입니다.

 

Observer Pattern

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

 

책에서 정의한 Observer Pattern 입니다. 하지만 이보다 더 중요하게 생각되는 것은 느슨한 결합 (Loose Coupling) 을 설명한 내용이라고 생각이 듭니다.  패턴의 정의를 설명하고 바로 뒤에 이어서 설명을 하고 있는데요, 느슨하게 결합되어 있다는 것은 서로 상호작용을 하지만 서로에 대해서는 잘 모른다는 것을 의미합니다. Observer Pattern 을 학습하면서 이 부분에 대해 생각을 하면 좋을 것 같습니다.

 

책의 예제를 활용하여 어떤 패턴인지 살펴보겠습니다.

 

먼저, 코드를 보기전에 Observer Pattern 이 무엇인지 개념을 한번 그려보겠습니다.

 

 

이처럼 주제 객체에서 일부 데이터를 관리하고, 데이터가 달라지면 옵저버한테 그 소식이 전해집니다. 신문이나 잡지의 구독을 생각해보면 이해가 쉽습니다. 신문사나 잡지사에 구독 신청을 하면 매번 새로운 신문/잡지가 나올 때마다 배달을 받을 수 있습니다. 계속 구독을 하고 있다면 계속해서 받을 수 있고, 해지를 했다면 더이상 신문이 오지 않죠.

 

옵저버 패턴도 이와 같습니다. 주제 객체가 신문사가 되고, 구독자가 옵저버가 되는 것이죠. 객체가 더이상 옵저버가 아니게 되면 데이터가 바뀔때 알 수 없는 것도 같습니다.

 

옵저버 패턴을 클래스 다이어그램으로 먼저 간단히  표현해보겠습니다.

  • Subject : 주제를 나타내는 Subject 인터페이스는 객체에서 옵저버로 등록하거나 옵저버 목록에서 탈퇴하고 싶을때 사용할 수 있는 메서드를 가지고 있습니다.
  • Observer : 옵저버가 될 가능성이 있는 객체에서는 이 Observer 인터페이스를 구현해야 하고, 인터페이스에는 주제의 상태가 바뀌었을 때, 호출 할 update 메서드를 가지고 있습니다.
  • ConcreteSubject : 주제 역할을 하는 구상 클래스에는 항상 Subject 인터페이스를 구현하고, 상태가 바뀔때마다 연락을 하기 위한 notifyObservers() 와 옵저버 등록 및 해지를 위한 메서드를 구현합니다.
  • ConcreteObserver : Observer 인터페이스만 구현한다면 무엇이든 옵저버 클래스가 될 수 있고, 각 옵저버는 특정 주제 객체에 등록을 해서 연락을 받을 수 있습니다.

이제 책의 예시에 있는 기상스테이션을 구현해봅시다. 기상 관측값이 갱신될 때마다 디스플레이 장비에서 그 값을 알아야 한다는 내용입니다. 위의 옵저버 패턴의 클래스 다이어그램을 생각해보며 설계를 해보겠습니다.

 

이 다이어그램을 바탕으로 구현을 해보겠습니다. 

 

먼저, 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 를 구현한 WeatherData 입니다.

import java.util.ArrayList;

public class WeatherData implements Subject{
   private ArrayList observers;
   private float temperature;
   private float humidity;
   private float pressure;

   public WeatherData() {
      observers = new ArrayList();
   }

   @Override
   public void registerObserver(Observer o) {
      observers.add(o);
   }

   @Override
   public void removeObserver(Observer o) {
      int i = observers.indexOf(o);
      if (i >= 0) {
         observers.remove(i);
      }
   }

   @Override
   public void notifyObservers() {
      for (Object o : observers) {
         Observer observer = (Observer)o;
         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();
   }
}

 

다음으로, Observer 를 구현한 CurrentConditionsDisplay 입니다. Forecast, StatisticsDisplay 등은 모두 비슷한 모양이라 이 객체만 대표로 보겠습니다.

 

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 degrees and " + humidity + "% humidity");
   }
}

 

이제 WeatherStation 에서 WeatherData 를 갱신해보고 그때마다 무슨일이 일어나는지 보겠습니다.

 

public class WeatherStation {
   public static void main(String[] args) {
      WeatherData weatherData = new WeatherData();

      CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);

      weatherData.setMeasurements(80, 65, 30.4f);
      weatherData.setMeasurements(82, 70, 29.2f);
      weatherData.setMeasurements(78, 90, 39.4f);
   }
}

 

 

상태가 바뀌는 것에 따라 CurrendConditionDisplay 의 update가 호출되면서 바뀐값을 출력해주고 있습니다.

 

그러면 옵저버가 잘 동작하는 것을 봤으니, 느슨한 결합에 대해 다시 이야기를 해보겠습니다. 느슨한 결합은 서로 상호작용은 하지만 서로에 대해서는 잘 모르는 상태를 의미합니다.

 

위의 예제를 보면서 더 자세히 설명해보면, Subject 를 구현한 구현체는 Observer 를 구현한 객체에 대해서는 아는 것이 없습니다.

단지 Observer 인터페이스를 구현한다는 것을 알뿐이죠. 또한 새로운 Observer 를 추가한다고 해도 Subject 쪽은 변경이 필요가 없습니다. 단지 Observer 로 등록만 해주면 되는 것이죠. 

 

이를 통해서 얻는 이점은 Subject 와 Observer 가 상호작용을 하지만 서로 영향을 미치지 않으며 독립적으로 사용이 가능하다는 점과 재사용성이 올라가는 점 입니다. 

 

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

이런 디자인을 사용하면 변경 사항이 생겨도 유연하게 처리할 수 있는 객체지향 시스템을 구축할 수 있습니다.