作者: andylin02
学习章节: 第2章 让你的对象知悉现状:观察者模式
关键词:观察者模式, Python, 松耦合, 一对多依赖, 主题, 订阅, 推拉模型, 设计原则, abc
观察者模式Python深度解析:让对象间自动同步的松耦合通信
大家好,我是 andylin02。在上一篇Python版策略模式的学习中,我们成功拯救了Joe的鸭子模拟器,让飞行和叫声行为可以灵活组合,深刻理解了“封装变化”、“多用组合少用继承”和“面向接口编程”。今天,我们进入第2章——观察者模式。如果你用过报纸订阅、关注过博主、或者用过Tweepy监听推文,那你就已经体验过观察者模式的核心思想。让我们用Python来实现一个气象站系统,看看对象之间如何实现优雅的一对多通知。
一、开篇复习:策略模式教会了我们什么?
上一章,我们从Joe的“鸭子灾难”出发,学会了三个核心设计原则:
- 找出变化的部分并封装之:将飞行和叫声从鸭子类中分离,形成独立的策略族。
- 面向接口编程,而非针对实现:鸭子依赖 FlyBehavior 和 QuackBehavior 抽象,不关心具体实现。
- 多用组合,少用继承:每个鸭子对象组合了行为对象,运行时可动态替换。
策略模式让我们能够灵活地互换算法。而今天要学的观察者模式,解决的则是另一种常见挑战:一个对象的状态一旦改变,需要自动通知多个依赖它的对象,而且这些依赖对象的数量和类型可能随时变化。这同样是封装变化、面向接口和松耦合思想的又一次实践。
二、故事开始:气象站的数据推送困境
你的团队接手一个气象监测系统。系统中有一个 WeatherData 对象,它从物理传感器获取温度、湿度和气压数据。每当数据更新,就需要立刻刷新三个显示面板:“当前状况”、“气象统计”和“天气预报”。产品经理暗示,未来可能还要增加“体感温度”、“风力预报”等新面板。
你最先想到的实现可能是这样的:
class WeatherData:
def __init__(self):
self.temperature = 0.0
self.humidity = 0.0
self.pressure = 0.0
def measurements_changed(self):
# 直接硬编码调用各个面板
temp = self.temperature
humidity = self.humidity
pressure = self.pressure
current_display.update(temp, humidity, pressure)
statistics_display.update(temp, humidity, pressure)
forecast_display.update(temp, humidity, pressure)
这种写法的问题非常明显:
这正是观察者模式要解决的经典问题:一个对象的状态改变时,需要自动通知多个依赖它的对象,而且希望主题和观察者之间是松耦合的。
三、出版者 + 订阅者 = 观察者模式
回想一下你订阅报纸的场景:你向报社订阅,报社把你的联系方式登记在册。每次新报纸出版,报社就把报纸送到你(和其他订阅者)手上。你可以随时取消订阅,之后就不再收到报纸。报社不需要知道你是谁、做什么工作,只要知道你提供了“收报纸”的接口(你的邮箱地址或门牌号)即可。
这个场景完美映射到观察者模式:
观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
在这个模式里,报社被称为 主题(Subject),订阅者是 观察者(Observer)。主题是拥有状态的一方,观察者依赖(或关注)主题的状态。它们之间通过接口解耦,新增观察者时主题代码无需改动。
观察者模式带来的设计原则:
为交互对象之间的松耦合设计而努力。
主题只知道观察者实现了某个接口,不需要知道观察者的具体类。这使得主题和观察者可以独立变化,互不影响。
四、观察者模式正式定义与UML类图
定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
Python视角的UML类图(我们用 abc 模块来定义抽象基类):
#mermaid-svg-hlRFxdgoY4ObDIb7{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hlRFxdgoY4ObDIb7 .error-icon{fill:#552222;}#mermaid-svg-hlRFxdgoY4ObDIb7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hlRFxdgoY4ObDIb7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .marker.cross{stroke:#333333;}#mermaid-svg-hlRFxdgoY4ObDIb7 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hlRFxdgoY4ObDIb7 p{margin:0;}#mermaid-svg-hlRFxdgoY4ObDIb7 g.classGroup text{fill:#9370DB;stroke:none;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-hlRFxdgoY4ObDIb7 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster-label text{fill:#333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster-label span{color:#333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster-label span p{background-color:transparent;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster text{fill:#333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .cluster span{color:#333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .nodeLabel,#mermaid-svg-hlRFxdgoY4ObDIb7 .edgeLabel{color:#131300;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-hlRFxdgoY4ObDIb7 .label text{fill:#131300;}#mermaid-svg-hlRFxdgoY4ObDIb7 .labelBkg{background:#ECECFF;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-hlRFxdgoY4ObDIb7 .classTitle{font-weight:bolder;}#mermaid-svg-hlRFxdgoY4ObDIb7 .node rect,#mermaid-svg-hlRFxdgoY4ObDIb7 .node circle,#mermaid-svg-hlRFxdgoY4ObDIb7 .node ellipse,#mermaid-svg-hlRFxdgoY4ObDIb7 .node polygon,#mermaid-svg-hlRFxdgoY4ObDIb7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hlRFxdgoY4ObDIb7 .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 g.clickable{cursor:pointer;}#mermaid-svg-hlRFxdgoY4ObDIb7 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-hlRFxdgoY4ObDIb7 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-hlRFxdgoY4ObDIb7 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-hlRFxdgoY4ObDIb7 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-hlRFxdgoY4ObDIb7 .dashed-line{stroke-dasharray:3;}#mermaid-svg-hlRFxdgoY4ObDIb7 .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-hlRFxdgoY4ObDIb7 #compositionStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #compositionEnd,#mermaid-svg-hlRFxdgoY4ObDIb7 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #dependencyStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #dependencyStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #extensionStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #extensionEnd,#mermaid-svg-hlRFxdgoY4ObDIb7 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #aggregationStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #aggregationEnd,#mermaid-svg-hlRFxdgoY4ObDIb7 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #lollipopStart,#mermaid-svg-hlRFxdgoY4ObDIb7 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 #lollipopEnd,#mermaid-svg-hlRFxdgoY4ObDIb7 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-hlRFxdgoY4ObDIb7 .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-hlRFxdgoY4ObDIb7 .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hlRFxdgoY4ObDIb7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hlRFxdgoY4ObDIb7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hlRFxdgoY4ObDIb7 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
通知
«abstract»
Subject
+register_observer(Observer) : void
+remove_observer(Observer) : void
+notify_observers() : void
«abstract»
Observer
+update(temp, humidity, pressure) : void
«abstract»
DisplayElement
+display() : void
WeatherData
-observers : list
-temperature : float
-humidity : float
-pressure : float
+register_observer(Observer) : void
+remove_observer(Observer) : void
+notify_observers() : void
+measurements_changed() : void
+set_measurements(float, float, float) : void
+get_temperature() : float
+get_humidity() : float
+get_pressure() : float
CurrentConditionsDisplay
+update(float, float, float) : void
+display() : void
StatisticsDisplay
+update(float, float, float) : void
+display() : void
ForecastDisplay
+update(float, float, float) : void
+display() : void
主题持有观察者列表,观察者可以选择持有主题引用(用于后续拉取数据)。这种设计让主题和观察者可以独立变化。
五、Python完整源码
我们用 Python 的 abc 模块来定义接口,用列表管理观察者。以下是气象站系统的完整实现:
from abc import ABC, abstractmethod
from typing import List
# ========== 主题接口 ==========
class Subject(ABC):
@abstractmethod
def register_observer(self, observer: 'Observer'):
pass
@abstractmethod
def remove_observer(self, observer: 'Observer'):
pass
@abstractmethod
def notify_observers(self):
pass
# ========== 观察者接口 ==========
class Observer(ABC):
@abstractmethod
def update(self, temperature: float, humidity: float, pressure: float):
pass
# ========== 显示接口 ==========
class DisplayElement(ABC):
@abstractmethod
def display(self):
pass
# ========== 气象数据类(具体主题) ==========
class WeatherData(Subject):
def __init__(self):
self.observers: List[Observer] = []
self.temperature = 0.0
self.humidity = 0.0
self.pressure = 0.0
def register_observer(self, observer: Observer):
self.observers.append(observer)
def remove_observer(self, observer: Observer):
if observer in self.observers:
self.observers.remove(observer)
def notify_observers(self):
for observer in self.observers:
observer.update(self.temperature, self.humidity, self.pressure)
def measurements_changed(self):
self.notify_observers()
def set_measurements(self, temperature: float, humidity: float, pressure: float):
self.temperature = temperature
self.humidity = humidity
self.pressure = pressure
self.measurements_changed()
# Getter,可用于“拉”模型
def get_temperature(self):
return self.temperature
def get_humidity(self):
return self.humidity
def get_pressure(self):
return self.pressure
# ========== 当前状况面板(具体观察者) ==========
class CurrentConditionsDisplay(Observer, DisplayElement):
def __init__(self, weather_data: Subject):
self.temperature = 0.0
self.humidity = 0.0
self.weather_data = weather_data
weather_data.register_observer(self)
def update(self, temperature: float, humidity: float, pressure: float):
self.temperature = temperature
self.humidity = humidity
self.display()
def display(self):
print(f"当前状况: {self.temperature}°C,湿度 {self.humidity}%")
# ========== 气象统计面板(具体观察者) ==========
class StatisticsDisplay(Observer, DisplayElement):
def __init__(self, weather_data: Subject):
self.max_temp = float('-inf')
self.min_temp = float('inf')
self.temp_sum = 0.0
self.num_readings = 0
weather_data.register_observer(self)
def update(self, temperature: float, humidity: float, pressure: float):
self.temp_sum += temperature
self.num_readings += 1
if temperature > self.max_temp:
self.max_temp = temperature
if temperature < self.min_temp:
self.min_temp = temperature
self.display()
def display(self):
avg = self.temp_sum / self.num_readings if self.num_readings > 0 else 0
print(f"气象统计: 平均温度 {avg:.1f}°C,最高 {self.max_temp}°C,最低 {self.min_temp}°C")
# ========== 天气预报面板(具体观察者) ==========
class ForecastDisplay(Observer, DisplayElement):
def __init__(self, weather_data: Subject):
self.current_pressure = 29.92
self.last_pressure = 0.0
weather_data.register_observer(self)
def update(self, temperature: float, humidity: float, pressure: float):
self.last_pressure = self.current_pressure
self.current_pressure = pressure
self.display()
def display(self):
print("天气预报: ", end="")
if self.current_pressure > self.last_pressure:
print("天气正在好转!")
elif self.current_pressure == self.last_pressure:
print("天气基本不变。")
else:
print("天气变冷,可能有雨。")
# ========== 测试气象站 ==========
if __name__ == "__main__":
weather_data = WeatherData()
current_display = CurrentConditionsDisplay(weather_data)
statistics_display = StatisticsDisplay(weather_data)
forecast_display = ForecastDisplay(weather_data)
print("===== 第1次测量 =====")
weather_data.set_measurements(26.5, 65.0, 30.4)
print("\\n===== 第2次测量 =====")
weather_data.set_measurements(28.0, 70.0, 29.9)
print("\\n>>> 移除气象统计面板…")
weather_data.remove_observer(statistics_display)
print("\\n===== 第3次测量 =====")
weather_data.set_measurements(25.0, 90.0, 29.4)
运行结果:
===== 第1次测量 =====
当前状况: 26.5°C,湿度 65.0%
气象统计: 平均温度 26.5°C,最高 26.5°C,最低 26.5°C
天气预报: 天气基本不变。
===== 第2次测量 =====
当前状况: 28.0°C,湿度 70.0%
气象统计: 平均温度 27.2°C,最高 28.0°C,最低 26.5°C
天气预报: 天气变冷,可能有雨。
>>> 移除气象统计面板…
===== 第3次测量 =====
当前状况: 25.0°C,湿度 90.0%
天气预报: 天气变冷,可能有雨。
观察可见:取消统计面板订阅后,它不再收到通知,而其余面板正常工作。这就是观察者模式动态管理依赖关系的威力,且整个过程中我们无需修改 WeatherData 的核心逻辑。
六、推模型 vs 拉模型,以及Python特色
上面的实现采用推模型:主题将全部数据一次性推送给观察者。优点是一步到位;缺点是可能推送观察者不需要的数据。
如果希望观察者按需拉取数据,可以采用拉模型:在 update 方法中传递主题自身引用,让观察者自己选择获取哪些数据。修改点如下:
class Observer(ABC):
@abstractmethod
def update(self, subject: Subject):
pass
# WeatherData 中 notify 改为:
def notify_observers(self):
for observer in self.observers:
observer.update(self)
# CurrentConditionsDisplay 中 update 改为拉取所需:
def update(self, subject: Subject):
if isinstance(subject, WeatherData):
self.temperature = subject.get_temperature()
self.humidity = subject.get_humidity()
self.display()
拉模型更灵活,也避免传递无用参数。不过在简单场景下推模型更为直观。
Python特色轻量实现:由于Python支持一等函数和动态类型,我们也可以完全不用抽象基类,直接使用回调函数列表来实现观察者:
class WeatherData:
def __init__(self):
self._observers = []
self.temperature = 0.0
self.humidity = 0.0
self.pressure = 0.0
def register(self, callback):
self._observers.append(callback)
def remove(self, callback):
if callback in self._observers:
self._observers.remove(callback)
def _notify(self):
for callback in self._observers:
callback(self.temperature, self.humidity, self.pressure)
def set_measurements(self, t, h, p):
self.temperature = t
self.humidity = h
self.pressure = p
self._notify()
# 使用简单的函数作为观察者
def current_display(temp, hum, press):
print(f"当前: {temp}°C, {hum}%")
wd = WeatherData()
wd.register(current_display)
wd.set_measurements(20.1, 55.0, 30.0)
这种实现更Pythonic,灵活轻量;但当观察者行为复杂时,使用类封装依然是更好的选择。
七、观察者模式如何体现设计原则?
| 封装变化 | 变化的是观察者的数量和类型;主题通过统一的 Observer 接口管理它们,新增/删除不影响主题核心代码。 |
| 面向接口编程 | 主题依赖 Observer 抽象,不依赖具体面板类;观察者依赖 Subject 抽象。 |
| 多用组合,少用继承 | 主题组合了一组观察者对象(列表),而非通过继承获取通知能力。 |
| 为松耦合设计而努力 | 主题和观察者只通过抽象接口交互,可以独立复用和变化,互相不知道具体实现。 |
| 开闭原则 | 对扩展开放:新增显示面板只需创建新的观察者类并注册;对修改封闭:主题无需任何更改。 |
MVC架构中模型(Model)与视图(View)之间的通知机制,正是观察者模式的典型应用。
八、观察者模式 vs 其他相似模式
1. 观察者模式 vs 策略模式
- 相同点:都基于接口实现解耦,都可以在运行时动态替换对象。
- 不同点:
- 意图:策略模式封装可互换的算法,由客户端主动选择;观察者模式定义对象间的一对多依赖,自动通知状态变化。
- 数据结构:策略通常是一对一(上下文持有一个策略);观察者是一对多(主题持有多个观察者)。
- 数据流:策略是单向“使用”;观察者是主题→观察者的广播通知。
- 比喻:策略像打车时选择“经济/舒适/豪华”车型;观察者像订阅了地震预警APP,一有警报多个手机同时收到通知。
2. 观察者模式 vs 发布-订阅模式
- 发布-订阅模式往往引入事件通道/消息代理(如Redis Pub/Sub、RabbitMQ),发布者和订阅者完全解耦,互不知晓。观察者模式中主题直接持有观察者列表,耦合度稍高,但实现简单。
- 在Python生态中,blinker 库提供了信号机制,就是一种典型的发布-订阅实现。
3. 观察者模式 vs 命令模式(前瞻)
- 命令模式将请求封装为对象,以支持参数化、排队、撤销等;观察者模式是状态改变时广播通知。命令模式关注“做什么”,观察者模式关注“状态同步”。
- 类比:命令是点了一份“炒饭”;观察者是餐厅内一旦新菜出炉,所有服务员都会收到提醒。
九、本章读书笔记与总结
核心知识点清单
- 观察者模式定义了对象间一对多的依赖关系,主题状态改变时自动通知所有观察者。
- 主题(Subject)维护观察者列表,状态更新时遍历通知。
- 观察者(Observer)实现统一的更新接口,可以动态注册/注销。
- 推模型:主题推送全部数据;拉模型:观察者通过主题引用拉取所需数据。
- 松耦合是观察者模式的最大价值:主题与观察者互相依赖抽象,可独立变化。
- Python中可以用抽象基类实现,也可用回调函数实现轻量级观察者。
- 适用于GUI事件、MVC架构、消息推送、实时数据监控等场景。
一张图理解观察者模式的数据流
#mermaid-svg-xMrM01SkOSsNy1Jx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xMrM01SkOSsNy1Jx .error-icon{fill:#552222;}#mermaid-svg-xMrM01SkOSsNy1Jx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xMrM01SkOSsNy1Jx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xMrM01SkOSsNy1Jx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xMrM01SkOSsNy1Jx .marker.cross{stroke:#333333;}#mermaid-svg-xMrM01SkOSsNy1Jx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xMrM01SkOSsNy1Jx p{margin:0;}#mermaid-svg-xMrM01SkOSsNy1Jx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster-label text{fill:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster-label span{color:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster-label span p{background-color:transparent;}#mermaid-svg-xMrM01SkOSsNy1Jx .label text,#mermaid-svg-xMrM01SkOSsNy1Jx span{fill:#333;color:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx .node rect,#mermaid-svg-xMrM01SkOSsNy1Jx .node circle,#mermaid-svg-xMrM01SkOSsNy1Jx .node ellipse,#mermaid-svg-xMrM01SkOSsNy1Jx .node polygon,#mermaid-svg-xMrM01SkOSsNy1Jx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xMrM01SkOSsNy1Jx .rough-node .label text,#mermaid-svg-xMrM01SkOSsNy1Jx .node .label text,#mermaid-svg-xMrM01SkOSsNy1Jx .image-shape .label,#mermaid-svg-xMrM01SkOSsNy1Jx .icon-shape .label{text-anchor:middle;}#mermaid-svg-xMrM01SkOSsNy1Jx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xMrM01SkOSsNy1Jx .rough-node .label,#mermaid-svg-xMrM01SkOSsNy1Jx .node .label,#mermaid-svg-xMrM01SkOSsNy1Jx .image-shape .label,#mermaid-svg-xMrM01SkOSsNy1Jx .icon-shape .label{text-align:center;}#mermaid-svg-xMrM01SkOSsNy1Jx .node.clickable{cursor:pointer;}#mermaid-svg-xMrM01SkOSsNy1Jx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xMrM01SkOSsNy1Jx .arrowheadPath{fill:#333333;}#mermaid-svg-xMrM01SkOSsNy1Jx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xMrM01SkOSsNy1Jx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xMrM01SkOSsNy1Jx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xMrM01SkOSsNy1Jx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xMrM01SkOSsNy1Jx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xMrM01SkOSsNy1Jx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster text{fill:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx .cluster span{color:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xMrM01SkOSsNy1Jx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xMrM01SkOSsNy1Jx rect.text{fill:none;stroke-width:0;}#mermaid-svg-xMrM01SkOSsNy1Jx .icon-shape,#mermaid-svg-xMrM01SkOSsNy1Jx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xMrM01SkOSsNy1Jx .icon-shape p,#mermaid-svg-xMrM01SkOSsNy1Jx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xMrM01SkOSsNy1Jx .icon-shape .label rect,#mermaid-svg-xMrM01SkOSsNy1Jx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xMrM01SkOSsNy1Jx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xMrM01SkOSsNy1Jx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xMrM01SkOSsNy1Jx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
注册观察者
包含
包含
包含
notify_observers
notify_observers
notify_observers
可拉取数据
主题 WeatherData
观察者列表
CurrentConditionsDisplay
StatisticsDisplay
ForecastDisplay
主题状态改变时,通知像涟漪一样扩散到所有注册的观察者,而主题完全不知道具体是谁在接收。
十、下篇预告:第3章 · 装饰者模式(Python版)
今天我们通过气象站案例完整掌握了观察者模式,让多个显示面板自动刷新而无需修改主题的核心逻辑。下一章我们将进入装饰者模式——想象你在咖啡店点了一杯深焙咖啡,可以动态地加入摩卡、奶泡、豆浆……每加一种配料,描述和价格都实时变化,而且可以任意组合。装饰者模式将让你在运行时给对象添加职责,完美体现“对扩展开放,对修改封闭”的原则。我们将用Python构建一个咖啡订单系统,还会对比Python内建的装饰器语法,敬请期待!
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

