自学内容网 自学内容网

【HF设计模式】02-观察者模式

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

《Head First设计模式》第2章笔记:结合示例应用和代码,介绍观察者模式,包括遇到的问题、应用的 OO 原则、达到的效果。


1 示例应用

示例应用是一个“气象观测站”,系统组成如下:

温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
湿度
气压
温度
传感器
气象站
湿度
传感器
气压
传感器
Weather
Data
当前天气
天气预报
气象统计
<<待扩展>>
热度指数
...
  1. 物理设备“气象站”:从传感器获取实时的气象数据,包括温度、湿度和气压;
  2. 第3方模块 WeatherData:一边获取“气象站”的气象数据,一边更新每个“显示组件”的气象数据;
  3. 需要开发的“显示组件”:包括当前天气组件、天气预报组件和气象统计组件;
    未来还会扩展其它“显示组件”。

第3方模块 WeatherData 提供的集成接口如下:

WeatherData
getTemperature()
getHumidity()
getPressure()
measurementsChanged()

其中 measurementsChanged() 方法需要我们来实现:

/*
 * 每当气象测量(weather measurements: 温度、湿度、气压)更新时,这个方法就会被调用
 */
public void measurementsChanged() {
  // TODO:需要添加方法实现
}

作为 WeatherData 的集成方,

  • 我们需要在 measurementsChanged() 方法被调用时,使用最新的测量数据更新所有的“显示组件”;
  • 我们不需要关注 measurementsChanged() 方法是“如何”被调用的。

2 遇到问题

在明确需求后,我们有了第1版 measurementsChanged() 的实现:

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);           // 更新“天气预报”显示组件
}

思考题

对于第1版 measurementsChanged() 实现,下列哪些说法是正确的?(多选)【答案在第 20 行】
Based on our first implementation, which of the following apply? (Choose all that apply.)

A. 针对具体实现编程,而不是针对接口编程。We are coding to concrete implementations, not interfaces.
B. 每次添加新的显示组件,都得修改这部分代码。For every new display we’ll need to alter this code.
C. 没有办法在运行时添加(或移除)显示组件。We have no way to add (or remove) display elements at runtime.
D. 这些显示组件没有实现一个共同的接口。The display elements don’t implement a common interface.
E. 没有对变化的部分进行封装。We haven’t encapsulated the part that changes.
F. 违反了 WeatherData 类的封装性。We are violating encapsulation of the WeatherData class.










答案:A B C E
解析:
A. 参考下文 “其它 OO 原则的应用” 部分
B. 需要添加对新的显示组件的调用 newDisplay.update()
C. 所有显示组件的调用都被硬编码
D. 使用了共同的 update(temp, humidity, pressure) 接口
E. 变化的部分包括:新的显示组件、添加(或移除)显示组件
F. 参考上一篇文章:01-策略模式 => 5.1 OO 基础 => 封装

3 引入设计模式

3.1 认识观察者模式

你知道报纸或杂志是怎么订阅的吗?

序号报社某读者
1开始运营,出版报纸
2想看报纸时:
向报社订阅,成为订阅者
3接受订阅,将“某读者”加入订阅者名单
4每当有新报纸出版时:
向名单中的每一个订阅者递送报纸
5收到报纸
6不想再看报纸时:
向报社取消订阅
7接受取消,将“某读者”移出订阅者名单
不再向其递送报纸

只要报社还在运营,就会一直有人、酒店、航班和其他企业订阅和取消订阅报纸。

出版者 + 订阅者 = 观察者模式

如果你了解报纸的订阅,就会在很大程度上理解观察者模式。只是在观察者模式中,将出版者叫作主题(SUBJECT),将订阅者叫作观察者(OBSERVERS)。

主题某对象序号报社某读者
开始运行,管理某些重要的数据1开始运营,出版报纸
想了解主题数据时:
向主题注册,成为观察者
2想看报纸时:
向报社订阅,成为订阅者
接受注册,将“某对象”加入观察者列表3接受订阅,将“某读者”加入订阅者名单
每当数据发生改变时:
向列表中的每一个观察者发送通知,
同时附带数据,或者需要自取数据
4每当有新报纸出版时:
向名单中的每一个订阅者递送报纸
得到数据5收到报纸
不想再了解主题数据时:
向主题取消注册
6不想再看报纸时:
向报社取消订阅
接受取消,将“某对象”移出观察者列表
不再向其发送通知
7接受取消,将“某读者”移出订阅者名单
不再向其递送报纸

3.2 观察者模式定义

观察者模式(Observer Pattern)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
Define a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

这里的“一对多”,是指一个主题对应多个观察者,当主题的数据发生改变时,所有观察者都会得到通知并被自动更新。

将模式应用于“气象观测站”:一个 WeatherData 对应多个“显示组件”,当 WeatherData 的气象测量值发生改变时,所有“显示组件”都会得到通知并被自动更新。正是我们期望的情况。

下面结合类图,进一步了解观察者模式的设计细节。

«abstract»
Subject
-List<Observer> observers
+registerObserver(Observer)
+removeObserver(Observer)
+notifyObservers() : CallUpdateOfEachObserver
«interface»
Observer
+update()
ConcreteSubject
-subjectState
+getState() : ReturnSubjectState
+setState()
ConcreteObserver
-ConcreteSubject subject
-observerState :ConsistentWithSubjectState
+update() : CallGetStateOfSubject
  1. 主题抽象类 Subject
    • 提供 registerObserver()removeObserver() 方法,用于注册和移除观察者,其中观察者为 Observer 接口类型;
    • 使用 observers 列表保存处于注册状态的观察者;
    • 提供 notifyObservers() 方法,用于向 observers 列表中的每一个观察者发送更新通知;
      在通知过程中,会统一调用 Observer 接口的 update() 方法。
      注:由于当前 mermaid 类图不支持 note,所以方法(method)的返回类型都被用于作为注释,如 CallUpdateOfEachObserver
  2. 观察者接口 Observer
    • 定义 update() 方法,由主题在发送更新通知时调用。
  3. 具体主题 ConcreteSubject,继承 Subject 抽象类:
    • 使用 subjectState 保存内部状态;
    • 提供 setState()getState() 方法,用于访问其内部状态;
    • 可以在 setState() 的内部直接调用 notifyObservers() 向观察者发送通知;
      也可以在更适当的时机(例如,在一系列的状态改变完成之后,即多次 setState() 之后),再发送通知。
  4. 具体观察者 ConcreteObserver,实现 Observer 接口:
    • 使用 observerState 保存内部状态;
    • 实现 update() 方法,获得主题的更新通知;
      在方法内部,可以通过具体主题的 getState() 获取主题状态,并同步更新其内部状态 observerState

延伸阅读:《设计模式:可复用面向对象软件的基础》 5.7 Observer(观察者)— 对象行为型模式 [P219-227]

3.3 OO 原则:松耦合设计

当两个对象之间松耦合时,它们可以交互,但是对彼此所知甚少。
When two objects are loosely coupled, they can interact, but they typically have very little knowledge of each other.

观察者模式实现了主题与观察者之间的松耦合,是松耦合设计的典范:

  1. 对于观察者,主题仅仅知道其实现了特定的接口(Observer 接口);
    • 主题不需要知道观察者的具体类是什么、能做什么,或者其它任何信息。
  2. 主题进行广播通信(notifyObservers()),依赖的是观察者的列表,而不是某(几)个观察者;
    • 可以在任何时候(包括运行时),添加观察者(registerObserver())或移除观察者(removeObserver())。
  3. 添加新类型的观察者时,不需要修改主题;
    • 只需要新的类型实现 Observer 接口,并通过 registerObserver() 注册成为观察者。
  4. 主题和观察者是独立封装,所以
    • 修改主题或观察者其中一方时,不会影响另一方(只要它们遵守彼此的接口约定);
    • 可以独立的复用主题,或独立的复用观察者。

因为对象之间的依赖降到了最低,所以松耦合的设计让我们能够创建“可以应对变化的、有弹性的 OO 系统”。
Loosely coupled designs allow us to build flexible OO systems that can handle change because they minimize the interdependency between objects.

设计原则
尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact.

3.4 其它 OO 原则的应用

3.4.1 封装变化

识别应用中变化的方面,把它们和不变的方面分开。
Identify the aspects of your application that vary and separate them from what stays the same.

  • 书上描述
    • 在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。使用这个模式,可以改变依赖于主题状态的对象,却不必改变主题。这叫作提前规划!
      The thing that varies in the Observer Pattern is the state of the Subject and the number and types of Observers. With this pattern, you can vary the objects that are dependent on the state of the Subject, without having to change that Subject. That’s called planning ahead!
  • 个人理解
    • 变化方面1:观察者的类型。封装方法:所有类型的观察者都实现 Observers 接口,通过统一的接口进行访问。
    • 变化方面2:观察者的数量。封装方法:将观察者放到 List 容器中,通过容器提供的统一的接口进行访问。

3.4.2 针对接口编程

针对接口编程,而不是针对实现编程。
Program to an interface, not an implementation.

主题和观察者都使用接口。主题使用观察者接口向观察者发送通知,而观察者则通过主题接口进行注册,以及获得通知。这使系统松散耦合,易于扩展和维护。
Both the Subject and Observers use interfaces. The Subject keeps track of objects implementing the Observer interface, while the Observers register with, and get notified by, the Subject interface. As we’ve seen, this keeps things nice and loosely coupled.

3.4.3 优先使用组合

优先使用组合而不是继承。
Favor composition over inheritance.

观察者模式使用“组合”来将任意数目的观察者组合进主题。观察者与主题之间的关系,不是通过继承建立的,而是在运行时通过组合的方式建立的。
The Observer Pattern uses composition to compose any number of Observers with their Subject. These relationships aren’t set up by some kind of inheritance hierarchy. No, they are set up at runtime by composition!

3.5 系统类图

使用观察者模式重新设计“气象观测站”,系统类图如下:

«interface»
Subject
registerObserver(Observer)
removeObserver(Observer)
notifyObservers()
WeatherData
List<Observer> observers
registerObserver(Observer)
removeObserver(Observer)
notifyObservers()
getTemperature()
getHumidity()
getPressure()
measurementsChanged()
setMeasurements()
«interface»
Observer
update()
«interface»
DisplayElement
display()
CurrentConditionsDisplay
WeatherData subject
update()
display()
StatisticsDisplay
WeatherData subject
update()
display()
ForecastDisplay
WeatherData subject
update()
display()
OtherDisplay
WeatherData subject
update()
display()
  1. WeatherData 是具体主题
    • 实现 Subject 接口,实现注册、移除和通知观察者的方法;
    • 提供设置和获取状态的方法。
  2. 各种“显示组件”,包括 CurrentConditionsDisplayStatisticsDisplayForecastDisplayOtherDisplay
    • 是具体观察者,实现 Observer 接口,实现 update() 方法,用于更新气象测量数据;
    • 实现 DisplayElement 接口,实现 display() 方法,用于更新显示界面。

新的 measurementsChanged() 实现如下:

public void measurementsChanged() {
    notifyObservers();  // 主题接口
}

public void notifyObservers() {
    for (Observer observer : observers) {  // 容器接口
        observer.update();  // 观察者接口
    }
}

通过采用观察者模式,第1版 measurementsChanged() 实现中的问题 都得到了解决,新的系统 可以应对变化并具有弹性

4 示例代码

4.1 Java 示例

相关接口定义:

// Observer.java
public interface Observer {
    // 通过 update 参数传递数据(由主题 push 数据),会有如下不足:(由观察者 pull 数据是另一种选择)
    // 1. 某些观察者类可能并不需要传递的所有数据
    // 2. 如果增加新的数据(如风速),需要修改所有观察者类的 update 方法
    public void update(float temperature, float humidity, float pressure);
}

// DisplayElement.java
public interface DisplayElement {
    public void display();
}

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

WeatherData 类定义:

// WeatherData.java
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.remove(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();
    }

    public float getTemperature() { return temperature; }
    public float getHumidity() { return humidity; }
    public float getPressure() { return pressure; }
}

“显示组件”类定义:

// CurrentConditionsDisplay.java
public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private float pressure;
    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;
        this.pressure = pressure;
        display();
    }

    public void display() {
        System.out.println("Current conditions: \n\t" + temperature
                + "F degrees, " + humidity + "% humidity, " + pressure + " pressure");
    }
}

// StatisticsDisplay.java
public class StatisticsDisplay implements Observer, DisplayElement {
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum = 0.0f;
    private int numReadings;
    private WeatherData weatherData;

    public StatisticsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    public void update(float temp, float humidity, float pressure) {
        tempSum += temp;
        numReadings++;

        if (temp > maxTemp) { maxTemp = temp; }
        if (temp < minTemp) { minTemp = temp; }

        display();
    }

    public void display() {
        System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
                + "/" + maxTemp + "/" + minTemp);
    }
}

测试代码:

// WeatherStation.java
public class WeatherStation {

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

        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);

        weatherData.removeObserver(currentDisplay);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

Java 曾经提供 Observable(Subject)类和 Observer 接口。但从 Java9 开始,不再赞成使用。

附上代码便于参考:

// Observer.java
public interface Observer {
    void update(Observable o, Object arg);
}

// Observable.java
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    public Observable() { obs = new Vector<>(); }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }
    public synchronized void deleteObservers() { obs.removeAllElements(); }
    public synchronized int countObservers() { return obs.size(); }

    public void notifyObservers() { notifyObservers(null); }

    public void notifyObservers(Object arg) {

        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into arbitrary code while holding its own Monitor.
             * The code where we extract each Observer from the Vector and store the state of the Observer
             * needs synchronization, but notifying observers does not (should not). 
             * The worst result of any potential race-condition here is that:
             * 1) a newly-added Observer will miss a notification in progress
             * 2) a recently unregistered Observer will be wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length - 1; i >= 0; i--)
            ((Observer) arrLocal[i]).update(this, arg);
    }

    public synchronized boolean hasChanged() { return changed; }
    protected synchronized void setChanged() { changed = true; }
    protected synchronized void clearChanged() { changed = false; }
}

4.2 C++11 示例

相关接口定义:

struct Observer {
  virtual ~Observer() = default;
  virtual void update(const class Subject& s) = 0;
};

struct DisplayElement {
  virtual ~DisplayElement() = default;
  virtual void display() const = 0;
};

struct Subject {
  virtual ~Subject() = default;

  virtual void registerObserver(std::shared_ptr<Observer> o) {
    for (const auto& observer : observers) {
      if (auto sp = observer.lock()) {
        if (sp == o) {
          return;
        }
      }
    }
    observers.push_back(o);
  }

  virtual void removeObserver(std::shared_ptr<Observer> o) {
    std::list<std::weak_ptr<Observer>>::const_iterator it;
    for (it = observers.begin(); it != observers.end(); ++it) {
      if (auto sp = it->lock()) {
        if (sp == o) {
          observers.erase(it);
          return;
        }
      }
    }
  }

  virtual void notifyObservers() {
    std::list<std::weak_ptr<Observer>>::const_iterator it;
    for (it = observers.begin(); it != observers.end();) {
      if (auto sp = it->lock()) {
        sp->update(*this);
        ++it;
      } else {
        it = observers.erase(it);
      }
    }
  }

 protected:
  Subject() = default;
  std::list<std::weak_ptr<Observer>> observers;
};

WeatherData 类定义:

struct WeatherData : public Subject {
  
  void measurementsChanged() { notifyObservers(); }

  void setMeasurements(float temperature, float humidity, float pressure) {
    this->temperature = temperature;
    this->humidity = humidity;
    this->pressure = pressure;
    measurementsChanged();
  }

  float getTemperature() const { return temperature; }
  float getHumidity() const { return humidity; }
  float getPressure() const { return pressure; }

 private:
  float temperature;
  float humidity;
  float pressure;
};

“显示组件”类定义:

struct CurrentConditionsDisplay : public Observer, public DisplayElement {

  void update(const Subject& s) override {
    const WeatherData* weatherData = dynamic_cast<const WeatherData*>(&s);
    if (weatherData) {
      temperature = weatherData->getTemperature();
      humidity = weatherData->getHumidity();
      pressure = weatherData->getPressure();
      display();
    }
  }

  void display() const override {
    std::cout << "Current conditions: \n\t" << temperature << "F degrees, "
              << humidity << "% humidity, " << pressure << " pressure\n";
  }

 private:
  float temperature;
  float humidity;
  float pressure;
};

struct StatisticsDisplay : public Observer, public DisplayElement {

  void update(const Subject& s) override {
    const WeatherData* weatherData = dynamic_cast<const WeatherData*>(&s);
    if (weatherData) {
      float temp = weatherData->getTemperature();
      tempSum += temp;
      numReadings++;

      if (temp > maxTemp) maxTemp = temp;
      if (temp < minTemp) minTemp = temp;

      display();
    }
  }

  void display() const override {
    std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings) << "/"
              << maxTemp << "/" << minTemp << "\n";
  }

 private:
  float maxTemp = 0.0f;
  float minTemp = 200;
  float tempSum = 0.0f;
  int numReadings = 0;
};

测试代码:

#include <iostream>
#include <list>
#include <memory>

// 在这里添加相关接口和类的定义

int main() {
  auto weatherData = std::make_shared<WeatherData>();
  auto currentDisplay = std::make_shared<CurrentConditionsDisplay>();
  auto statisticsDisplay = std::make_shared<StatisticsDisplay>();

  weatherData->registerObserver(currentDisplay);
  weatherData->registerObserver(statisticsDisplay);

  weatherData->setMeasurements(80, 65, 30.4f);
  weatherData->setMeasurements(82, 70, 29.2f);

  weatherData->removeObserver(currentDisplay);
  weatherData->setMeasurements(78, 90, 29.2f);
}

5 设计工具箱

5.1 OO 基础

OO 基础回顾

  1. 抽象(Abstraction)
  2. 封装(Encapsulation)
  3. 继承(Inheritance)
  4. 多态(Polymorphism)

5.2 OO 原则

5.2.1 新原则

尽量做到交互对象之间的松耦合设计。
Strive for loosely coupled designs between objects that interact.

5.2.2 原则回顾

  1. 封装变化。
    Encapsulate what varies.
  2. 针对接口编程,而不是针对实现编程。
    Program to interfaces, not implementations.
  3. 优先使用组合,而不是继承。
    Favor composition over inheritance.

5.3 OO 模式

5.3.1 新模式

观察者模式:

  • 定义对象之间的一对多依赖,
    The Observer Pattern defines a one-to-many dependency between objects
  • 这样一来,当一个对象改变状态时,它的所有依赖者都会被通知并自动更新。
    so that when one object changes state, all of its dependents are notified and updated automatically.

5.3.2 模式回顾

策略模式:

  • 定义一个算法家族,把其中的算法分别封装起来,使得它们之间可以互相替换。
    Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.
  • 让算法的变化独立于使用算法的客户。
    Strategy lets the algorithm vary independently from clients that use it.

参考

  1. [美]弗里曼、罗布森著,UMLChina译.Head First设计模式.中国电力出版社.2022.2
  2. [美]伽玛等著,李英军等译.设计模式:可复用面向对象软件的基础.机械工业出版社.2019.3
  3. wickedlysmart: Head First设计模式 Java 源码

Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.


原文地址:https://blog.csdn.net/learnandimprove/article/details/144316189

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!