重溫《深入淺出設計模式》觀察者模式 (Book Review of Head First Design Pattern, Observer Pattern)

2020-06-29

設計模式不會讓直觀上開發速度變得更快,反而在開發時會需要夠多的時間進行思考,然後才動手。但它的效益應該是當要維護、要擴充時,才能夠體現的,因為有良好的設計模式,程式碼的架構是充滿彈性且易於回應需求的,相較快速完成的架構可能缺少擴充的彈性,在面對需求時只能砍掉重練,或者是提心吊膽的修改程式碼害怕牽一髮動全身。無疑地如果在有需求變動的情境,使用設計模式先苦後甘是最好的選擇。

logo

觀察者模式

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

範例是以實作 IOT (Subject) 裝置更新數據後,通知訂閱該數據的裝置 (Observer) 根據變動的數據做出更新。

Before Pattern

在模式之前直觀的做法就是將更新的數據依序傳入到觀察者中,但這樣的做法會有高耦合的問題,因為有新的觀察裝置加入 WeatherData 的程式碼就必須擴充改寫。

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

    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDiDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);
  }
}

After Pattern

觀察者模式首先定義 Subject Interface 與 Observer Interface,在關係上 Subject 擁有多個 Observer,並且 Subject 擁有 add, remove 方法來增減 Observer Instance,當 Subject 改變了,相依它的觀察者就會被通知並且被更新,通知及更新的動作是由 Subject 來執行的。

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(); 
}

實踐介面

public class WeatherData : Subject
{
  private readonly ArrayList<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)
  {
    int i = observers.indexOf(o);
    if (i >= 0)
    {
      observers.removeAt(i);
    }
  }
}

public void notifyObservers()
{
  foreach (var observer in observers)
  {
    observer.update(temperature, humidity, pressure);
  }
}

public setMeasutrements(float temperature, float humidity, float pressure)
{
  this.temperature = temperature;
  this.humidity = humidity;
  this.pressure = pressure;

  notifyObservers();
}

實作一項觀察者類別

public class CurrentConditionsDisplay : Observer, DisplayElement
{
  private float temperature;
  private float humidity;
  private float weatherData;

  public CurrentConditionsDisplay(Subject weatherData)
  {
    this.weatherData = weatherData;
    weatherData.registerObserver(this); // 建構時自動向 Subject 註冊
  }
  public void update(float temperature, float humidity, float pressure)
  {
    // 各觀察者類別獨特之處表現在不同的 update 邏輯上
    this.temprature = temprature;
    this.humidity = humidity;
    display();
  }

  public void display()
  {
    ...
  }
}

流程測試

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

    weatherData.setMeasurements(85, 68, 36);
  }
}

優點

  1. Subject 只知道 Observer 有實踐特定介面
  2. 任何時候都可以加入新的 Observer
  3. 有新類別的 Observer 出現時,不需要修改 Subject 的程式碼
  4. Subject 與 Observer 可以個別在不同的情境需求 Reuse
  5. 改變 Subject 與 Observer 不會影響到對方

小結

✔️將變動的部分封裝起來

  • Observer 種類是會變動的
  • Sbuject 的邏輯是會變動的

✔️多用合成,少用繼承

  • Sbuject 擁有多個 Observer
  • Observer 實作擁有一個 Sbuject 實作

✔️針對介面撰寫,而非針對實踐方式撰寫。

  • 演算邏輯分別跟著 Subject 及 Observer 類別

✔️讓需要互動的物件之間的關係鬆綁

  • 原本兩者 Subject 及 Observer 有高耦合,拆分之後自由奔放卻又合作無間