16、观察者模式实现(一):基于Python内置的Observer模式(使用列表管理观察者)
观察者模式,说白了就是「发布-订阅」那一套。一个对象状态变了,其他一堆对象要跟着更新。我最早接触这个模式是在做实时监控系统的时候,那时候还不知道这叫观察者模式,只是觉得「数据变了,界面得刷新」这个需求太常见了。
今天咱们先聊最朴素的实现方式——用列表来管理观察者。Python 内置的机制其实已经帮我们铺好了路,你甚至不需要额外装任何库。
观察者模式的核心三要素
要理解这个模式,你记住三个角色就够了:
- 主题(Subject):被观察的对象,它持有观察者列表,负责通知
- 观察者(Observer):订阅主题的对象,收到通知后执行自己的逻辑
- 通知机制:主题遍历观察者列表,逐个调用更新方法
嗯,这里要注意:观察者模式不是「推」就是「拉」。推模式是主题把数据直接传给观察者,拉模式是观察者自己来取。我个人习惯用推模式,因为接口更清晰,观察者不需要知道主题的内部结构。
用列表管理观察者:最直观的实现
Python 里实现这个太简单了。一个列表,几个方法,搞定。我在项目中遇到过好几次这样的场景:用户登录状态变了,多个模块(导航栏、权限菜单、个人中心)都要刷新。用观察者模式,代码瞬间清爽。
来看代码:
class Subject:
"""主题类,管理观察者列表"""
def __init__(self):
self._observers = [] # 观察者列表
def attach(self, observer):
"""注册观察者"""
if observer not in self._observers:
self._observers.append(observer)
print(f"观察者 {observer.name} 已注册")
def detach(self, observer):
"""注销观察者"""
try:
self._observers.remove(observer)
print(f"观察者 {observer.name} 已注销")
except ValueError:
print("观察者不存在")
def notify(self, message=None):
"""通知所有观察者"""
print(f"主题发出通知: {message}")
for observer in self._observers:
observer.update(message)
class Observer:
"""观察者基类"""
def __init__(self, name):
self.name = name
def update(self, message):
"""收到通知后的更新方法,子类重写"""
raise NotImplementedError("子类必须实现 update 方法")
class UserObserver(Observer):
"""具体的观察者:用户模块"""
def update(self, message):
print(f"[{self.name}] 收到通知: {message},正在刷新用户信息...")
class MenuObserver(Observer):
"""具体的观察者:菜单模块"""
def update(self, message):
print(f"[{self.name}] 收到通知: {message},正在更新菜单权限...")
class LogObserver(Observer):
"""具体的观察者:日志模块"""
def update(self, message):
print(f"[{self.name}] 收到通知: {message},正在记录日志...")
你看,代码量不大。主题只管维护列表和遍历通知,观察者只管实现自己的 update 逻辑。这就是「开闭原则」的体现——新增一个观察者,不需要改主题的代码。
运行一下看看效果
if __name__ == "__main__":
# 创建主题
subject = Subject()
# 创建观察者
user_obs = UserObserver("用户模块")
menu_obs = MenuObserver("菜单模块")
log_obs = LogObserver("日志模块")
# 注册观察者
subject.attach(user_obs)
subject.attach(menu_obs)
subject.attach(log_obs)
# 触发通知
subject.notify("用户登录成功")
print("\n--- 注销菜单观察者 ---")
subject.detach(menu_obs)
subject.notify("用户退出登录")
输出结果:
观察者 用户模块 已注册
观察者 菜单模块 已注册
观察者 日志模块 已注册
主题发出通知: 用户登录成功
[用户模块] 收到通知: 用户登录成功,正在刷新用户信息...
[菜单模块] 收到通知: 用户登录成功,正在更新菜单权限...
[日志模块] 收到通知: 用户登录成功,正在记录日志...
--- 注销菜单观察者 ---
观察者 菜单模块 已注销
主题发出通知: 用户退出登录
[用户模块] 收到通知: 用户退出登录,正在刷新用户信息...
[日志模块] 收到通知: 用户退出登录,正在记录日志...
为什么会这样?因为列表里少了一个观察者,通知时自然就跳过了。你想想看,这种动态增删观察者的能力,在实际系统中多有用——比如用户关闭了某个面板,就不用再通知它了。
这个实现有什么坑?
- 观察者列表的线程安全问题:如果在多线程环境下遍历列表时,另一个线程删除了观察者,会抛出 RuntimeError。解决方案是用锁或者拷贝列表。
- 循环通知:观察者的 update 方法里又去修改主题,导致无限递归。我建议在通知时加一个递归深度限制。
- 内存泄漏:观察者注册后忘记注销,主题持有对观察者的引用,导致 GC 无法回收。尤其是用 lambda 或闭包做观察者时,更要小心。
weakref 来管理观察者列表,这样观察者被销毁后自动从列表中移除,省去手动 detach 的麻烦。
用 SVG 画一张结构图
下面这张图展示了观察者模式的核心流程,我建议你多看几遍,理解「注册-通知-更新」这个闭环:
什么时候用这种实现?
说实话,用列表管理观察者是最基础的方式,适合以下场景:
- 观察者数量不多(几十个以内)
- 通知频率不高(不是每毫秒都在通知)
- 不需要复杂的过滤或优先级排序
- 你只是想快速实现一个发布-订阅机制
我在做内部工具的时候经常用这种方式,简单直接,出了问题也好排查。但如果是高并发、高频率的场景,我会考虑用事件队列或者消息中间件,这个后面再聊。
总结一下
用 Python 列表实现观察者模式,核心就三件事:
- 主题维护一个
_observers列表 - 提供
attach和detach方法管理观察者 - 通知时遍历列表,调用每个观察者的
update
说白了,这就是一个「回调函数列表」的面向对象版本。你想想看,是不是这个理?
📌 记住这个原则:观察者模式的核心是「解耦」。主题不需要知道观察者是谁,观察者也不需要知道主题内部怎么实现。它们只通过一个 update 接口通信。
下一节我们会在这个基础上,引入 Python 的 weakref 来解决内存泄漏问题,让实现更健壮。但今天这个版本,已经足够你应付大部分日常需求了。
公众号:蓝海资料掘金营,微信deep3321