欢迎光临
我们一直在努力

【Python】《Head First设计模式》读书笔记03: 观察者模式Python深度解析 - 让对象间自动同步的松耦合通信

作者: 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)

这种写法的问题非常明显:

  • 针对具体实现编程:WeatherData 直接依赖具体的面板对象,新增或删除面板必须修改 measurements_changed 方法。
  • 无法在运行时动态增删面板:面板被硬编码在代码里,程序启动后无法灵活订阅或取消订阅。
  • 没有封装变化:变化的是“哪些面板需要更新”以及“面板的类型”,但代码把它们和主题牢牢绑在一起。
  • 这正是观察者模式要解决的经典问题:一个对象的状态改变时,需要自动通知多个依赖它的对象,而且希望主题和观察者之间是松耦合的。

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

    回想一下你订阅报纸的场景:你向报社订阅,报社把你的联系方式登记在册。每次新报纸出版,报社就把报纸送到你(和其他订阅者)手上。你可以随时取消订阅,之后就不再收到报纸。报社不需要知道你是谁、做什么工作,只要知道你提供了“收报纸”的接口(你的邮箱地址或门牌号)即可。

    这个场景完美映射到观察者模式:

    观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

    在这个模式里,报社被称为 主题(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内建的装饰器语法,敬请期待!


    本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
    👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

    赞(0)
    未经允许不得转载:171主机测评 » 【Python】《Head First设计模式》读书笔记03: 观察者模式Python深度解析 - 让对象间自动同步的松耦合通信
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址