

《Head First 设计模式》学习笔记 | 观察者模式
source link: http://jalan.space/2020/03/30/2020/design-pattern-observer/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

《Head First 设计模式》学习笔记 | 观察者模式
0 Comments往期回顾:
设计气象观测站
以书中的气象监测应用为例:现在有一个气象中心可以监测温度、湿度、气压三种数据,我们需要通过 WeatherData
对象来获取这些数据,然后将这些数据显示在特定的装置上。
WeatherData
拥有以下方法:
getTemperature()
:获取温度数据getHumidity()
: 获取湿度数据getPressure()
:获取气压数据measurementsChanged()
:一旦气象站更新数据,这个方法会被调用
这样一看似乎十分简单:我们只要在 measurementsChanged()
中通过一系列 getter
获取到气象台提供的温度、湿度与气压数据,然后再调用显示装置的更新数据方法即可。
但是,如果我们后续需要增加或减少显示装置应该怎么办呢?每次都要修改 measurementsChanged()
显然不是个好办法。
出版者与订阅者
想想在现实生活中我们是怎么享受报纸订阅服务的?
- 报社负责出版报纸,可以接受人们的订阅或取消订阅
- 如果我们向报社订阅了报纸,一旦有新报纸出版,报社就会送来新的报纸
- 如果我们不想看报纸了,就取消订阅,报社就不会再送新报纸上门
气象站与显示装置之间其实也是这样的关系,气象站为「出版者」,显示装置为「订阅者」:需要获得气象站数据的显示装置可以向气象站申请「订阅」,这样一旦有气象数据更新,气象站就会通知申请订阅的显示装置;如果显示装置不再需要该气象站提供数据,则可以「取消订阅」,不再接受气象站的通知。
上述「出版者」称为「主题」(Subject),「订阅者」称为「观察者」(Observer),两者构成了观察者模式的主要部分。
定义观察者模式
观察者模式定义如下:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
观察者模式类图
类图中包含两个接口定义:
- 主题接口
Subject
:registerObserver()
:添加订阅者removeObserver()
:移除订阅者notifyObserver()
:通知订阅者
- 观察者接口
Observer
:update()
:在主题notifyObserver()
中被调用,用于更新观察者的数据
实现气象站
根据上述观察者模式定义,我们先为气象站设计「主题」与「观察者」两个接口,除此之外也可以添加一个显示装置接口,专门负责显示装置的具体显示格式。
接口定义好后,就可以让具体的类来实现这些接口了:
WeatherData
作为具体主题,实现Subject
主题接口- 各个显示装置作为具体观察者,实现
Observer
观察者接口
气象站类图
<?php
/**
* 主题接口
*/
interface Subject
{
public function registerObserver($observer);
public function removeObserver($observer);
public function notifyObservers();
}
/**
* 观察者接口
*/
interface Observer
{
public function update($temp, $humidity, $pressure);
}
/**
* 显示装置显示接口
*/
interface DisplayElement
{
public function display();
}
/**
* WeatherData 实现主题接口
*/
class WeatherData implements Subject
{
// 观察者数组
private $observers;
// 温度
private $temperature;
// 湿度
private $humidity;
// 气压
private $pressure;
public function __construct()
{
$this->observers = [];
}
/**
* 加入新的观察者
*/
public function registerObserver($observer)
{
$this->observers[] = $observer;
}
/**
* 移除观察者
*/
public function removeObserver($observer)
{
$index = array_search($observer, $this->observers);
unset($this->observers[$index]);
}
/**
* 通知观察者
*/
public function notifyObservers()
{
// 遍历数组通知观察者
foreach ($this->observers as $observer) {
$observer->update($this->temperature, $this->humidity, $this->pressure);
}
}
public function measurementsChanged()
{
// 通知订阅者
$this->notifyObservers();
}
/**
* 气象站有新数据将调用该函数
*/
public function setMeasurements($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->pressure = $pressure;
$this->measurementsChanged();
}
}
// 创建显示装置
/**
* 显示装置 1: 只显示温度和湿度
*/
class FirstDisplay implements Observer, DisplayElement
{
// 温度
private $temperature;
// 湿度
private $humidity;
/**@var WeatherData $weatherData */
private $weatherData;
public function __construct($weatherData)
{
// 创建一个 WeatherData 实例
$this->weatherData = $weatherData;
$this->weatherData->registerObserver($this);
}
public function update($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->display();
}
public function display()
{
echo "当前温度:{$this->temperature},当前湿度:{$this->humidity}\n";
}
}
/**
* 显示装置 2:只显示气压
*/
class SecondDisplay implements Observer, DisplayElement {
// 气压
private $pressure;
/**@var WeatherData $weatherData */
private $weatherData;
public function __construct($weatherData)
{
// 创建一个 WeatherData 实例
$this->weatherData = $weatherData;
$this->weatherData->registerObserver($this);
}
public function update($temperature, $humidity, $pressure)
{
$this->pressure = $pressure;
$this->display();
}
public function display()
{
echo "当前气压:{$this->pressure}\n";
}
}
/**
* 显示装置 3(略)
* class ThirdDisplay
*/
// 测试调用
// 创建一个 WeatherData 对象
$weatherData = new WeatherData();
// 创建显示装置 1,传入 WeatherData 对象
$firstDisplay = new FirstDisplay($weatherData);
// 传入模拟气象数据
$weatherData->setMeasurements(80, 70, 30.4);
$weatherData->setMeasurements(70, 60, 29.2);
// 取消订阅
$weatherData->removeObserver($firstDisplay);
// 创建显示装置 2,传入 WeatherData 对象
$secondDisplay = new SecondDisplay($weatherData);
$weatherData->setMeasurements(90, 60, 29.2);
/* Output:
当前温度:80,当前湿度:70
当前温度:70,当前湿度:60
当前气压:29.2
*/
Python
class Subject:
def __init__(self):
"""
主题(出版者)
"""
self._observers = []
def register(self, observer):
"""
添加观察者
"""
if observer not in self._observers:
self._observers.append(observer)
def remove(self, observer):
"""
移除观察者
"""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self):
"""
发送通知给所有观察者
"""
for observer in self._observers:
observer.update()
class WeatherData(Subject):
def __init__(self):
Subject.__init__(self)
self._temperature = 0 # 温度
self._humidity = 0 # 湿度
self._pressure = 0 # 气压
def set_measurements(self, temperature, humidity, pressure):
"""
气象数据发生变动时调用该函数
"""
self._temperature = temperature
self._humidity = humidity
self._pressure = pressure
self.notify()
@property
def temperature(self):
return self._temperature
@property
def humidity(self):
return self._humidity
@property
def pressure(self):
return self._pressure
class FirstDisplay:
def __init__(self, weatherData):
"""
显示装置 1:显示温度和湿度
"""
self._weather_data = weatherData
self._weather_data.register(self)
self._temperature = 0
self._humidity = 0
def update(self):
"""
更新数据
"""
self._temperature = self._weather_data.temperature
self._humidity = self._weather_data.humidity
self.display()
def display(self):
"""
显示数据
"""
print("当前温度:%s,当前湿度:%s" % (self._temperature, self._humidity))
class SecondDisplay:
def __init__(self, weatherData):
"""
显示装置 2:显示气压
"""
self._weather_data = weatherData
self._weather_data.register(self)
self._pressure = 0
def update(self):
"""
更新数据
"""
self._pressure = self._weather_data.pressure
self.display()
def display(self):
print("当前气压:%s" % self._pressure)
def main():
# 创建一个 WeatherData 对象
weather_data = WeatherData()
# 创建显示装置 1
first_display = FirstDisplay(weather_data)
# 传入模拟数据
weather_data.set_measurements(21, 50, 3)
weather_data.set_measurements(3, 70, 4)
# 移除
weather_data.remove(first_display)
# 添加装置 2
second_display = SecondDisplay(weather_data)
weather_data.set_measurements(21, 50, 30)
if __name__ == "__main__":
main()
"""
Output:
当前温度:21,当前湿度:50
当前温度:3,当前湿度:70
当前气压:30
"""
Golang
package main
import (
"fmt"
)
// Subject 主题
type Subject interface {
Register(observer Observer)
Remove(obeserver Observer)
Notify()
}
// WeatherData 具体主题
type WeatherData struct {
observers []Observer
temperature int
humidity int
pressure float32
}
// 注册
func (w *WeatherData) Register(observer Observer) {
w.observers = append(w.observers, observer)
}
// 取消订阅
func (w *WeatherData) Remove(observer Observer) {
// 双指针法:找到需要取消订阅的对象并覆盖
j := 0
for _, ob := range w.observers {
if ob != observer {
w.observers[j] = observer
j++
}
}
w.observers = w.observers[:j]
}
// 通知所有订阅者
func (w *WeatherData) Notify() {
for _, observer := range w.observers {
observer.update(w.temperature, w.humidity, w.pressure)
}
}
// 设置新的数据
func (w *WeatherData) SetMeasurements(temperature int, humidity int, pressure float32) {
w.temperature = temperature
w.humidity = humidity
w.pressure = pressure
w.Notify()
}
// Observer 观察者
type Observer interface {
update(temperature int, humidity int, pressure float32)
display()
}
// FirstDisplay 显示装置 1
type FirstDisplay struct {
temperature int
humidity int
pressure float32
}
func (display *FirstDisplay) update(temperature int, humidity int, pressure float32) {
display.temperature = temperature
display.humidity = humidity
display.pressure = pressure
display.display()
}
func (display *FirstDisplay) display() {
fmt.Printf("当前温度:%d, 当前湿度:%d\n", display.temperature, display.humidity)
}
// SecondDisplay 显示装置 2
type SecondDisplay struct {
temperature int
humidity int
pressure float32
}
func (display *SecondDisplay) update(temperature int, humidity int, pressure float32) {
display.temperature = temperature
display.humidity = humidity
display.pressure = pressure
display.display()
}
func (display *SecondDisplay) display() {
fmt.Printf("当前气压:%.2f\n", display.pressure)
}
func main() {
weatherData := WeatherData{}
// 创建显示装置 1
firstDisplay := &FirstDisplay{}
weatherData.Register(firstDisplay)
weatherData.SetMeasurements(23, 50, 23.1)
// weatherData.Remove(firstDisplay)
// 创建显示装置 2
secondDisplay := &SecondDisplay{}
weatherData.Register(secondDisplay)
weatherData.SetMeasurements(22, 70, 24.2)
}
- 观察者模式定义了对象之间一对多的关系
- 主题通过一个共同的接口来更新观察者
- 主题和观察者之间用松耦合方式结合,主题不需要知道观察者的细节,具体观察者只需要实现观察者的接口
- 本文作者:江五渣。欢迎关注我的公众号:「编程拯救世界」,在编程世界一起冒险,一起成长!
- 本文链接:http://jalan.space/2020/03/30/2020/design-pattern-observer/
- 版权声明:本博客所有文章除特别声明外,均采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK