news 2026/4/23 13:00:13

Python 访问者模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 访问者模式

Python 中的访问者模式(Visitor Pattern)

访问者模式是一种行为型设计模式,其核心目的是:
将算法(操作)与对象结构分离,让你在不改变对象结构的前提下,为该结构中的元素添加新的操作。

形象比喻:就像一个动物园(对象结构)里有很多动物(元素),来了不同的“访客”(访问者)——摄影师会拍照、饲养员会喂食、兽医会检查健康。动物本身不需要改变,就能支持不同的新操作。

为什么需要访问者模式?

当你有一个稳定的对象结构(例如 AST 抽象语法树、图形元素树、文件系统),但需要频繁添加新操作时:

  • 如果用继承:在每个类中添加新方法 → 违反开闭原则
  • 如果用条件判断:分散在各个类中 → 难以维护

访问者模式通过双分派(Double Dispatch)解决这个问题:第一次分派决定访问者,第二次分派决定具体元素。

典型应用场景
  • 编译器:对 AST(抽象语法树)进行不同操作(类型检查、代码生成、优化、打印)
  • 文档转换:将结构化文档转为 HTML、PDF、Markdown
  • 图形渲染:对图形元素树执行绘制、计算面积、序列化等操作
  • 报表统计:在组织架构树上统计人数、薪资等
  • XML/JSON 处理:遍历 DOM 树执行不同操作
Python 实现示例:图形元素访问者

我们实现一个简单的图形编辑器,支持圆形和矩形,访问者包括:绘制、计算面积、导出 XML。

fromabcimportABC,abstractmethodfromtypingimportListimportxml.etree.ElementTreeasET# === 元素接口(Element)===classShape(ABC):@abstractmethoddefaccept(self,visitor):pass# 具体元素1:圆形classCircle(Shape):def__init__(self,x:float,y:float,radius:float):self.x,self.y,self.radius=x,y,radiusdefaccept(self,visitor):returnvisitor.visit_circle(self)# 具体元素2:矩形classRectangle(Shape):def__init__(self,x:float,y:float,width:float,height:float):self.x,self.y,self.width,self.height=x,y,heightdefaccept(self,visitor):returnvisitor.visit_rectangle(self)# === 访问者接口(Visitor)===classShapeVisitor(ABC):@abstractmethoddefvisit_circle(self,circle:Circle):pass@abstractmethoddefvisit_rectangle(self,rectangle:Rectangle):pass# 具体访问者1:绘制访问者classDrawVisitor(ShapeVisitor):defvisit_circle(self,circle:Circle):print(f"绘制圆形:中心({circle.x},{circle.y}), 半径{circle.radius}")defvisit_rectangle(self,rectangle:Rectangle):print(f"绘制矩形:左上角({rectangle.x},{rectangle.y}), "f"宽{rectangle.width}, 高{rectangle.height}")# 具体访问者2:面积计算访问者classAreaVisitor(ShapeVisitor):def__init__(self):self.total_area=0.0defvisit_circle(self,circle:Circle):importmath area=math.pi*circle.radius**2self.total_area+=areaprint(f"圆形面积:{area:.2f}")defvisit_rectangle(self,rectangle:Rectangle):area=rectangle.width*rectangle.height self.total_area+=areaprint(f"矩形面积:{area:.2f}")# 具体访问者3:XML 导出访问者classXMLExportVisitor(ShapeVisitor):def__init__(self):self.root=ET.Element("shapes")defvisit_circle(self,circle:Circle):elem=ET.SubElement(self.root,"circle")elem.set("x",str(circle.x))elem.set("y",str(circle.y))elem.set("radius",str(circle.radius))defvisit_rectangle(self,rectangle:Rectangle):elem=ET.SubElement(self.root,"rectangle")elem.set("x",str(rectangle.x))elem.set("y",str(rectangle.y))elem.set("width",str(rectangle.width))elem.set("height",str(rectangle.height))defget_xml(self)->str:returnET.tostring(self.root,encoding='unicode')# 对象结构:画布(可以包含多个图形)classCanvas:def__init__(self):self.shapes:List[Shape]=[]defadd(self,shape:Shape):self.shapes.append(shape)defaccept(self,visitor:ShapeVisitor):forshapeinself.shapes:shape.accept(visitor)# 客户端使用if__name__=="__main__":canvas=Canvas()canvas.add(Circle(10,20,5))canvas.add(Rectangle(30,40,15,10))canvas.add(Circle(50,50,8))print("=== 绘制所有图形 ===")draw_visitor=DrawVisitor()canvas.accept(draw_visitor)print("\n=== 计算总面积 ===")area_visitor=AreaVisitor()canvas.accept(area_visitor)print(f"总面积:{area_visitor.total_area:.2f}")print("\n=== 导出为 XML ===")xml_visitor=XMLExportVisitor()canvas.accept(xml_visitor)print(xml_visitor.get_xml())

输出

=== 绘制所有图形 === 绘制圆形:中心(10, 20), 半径 5 绘制矩形:左上角(30, 40), 宽 15, 高 10 绘制圆形:中心(50, 50), 半径 8 === 计算总面积 === 圆形面积: 78.54 矩形面积: 150.00 圆形面积: 201.06 总面积: 429.60 === 导出为 XML === <shapes><circle x="10" y="20" radius="5" /><rectangle x="30" y="40" width="15" height="10" /><circle x="50" y="50" radius="8" /></shapes>
访问者模式结构总结
角色说明
Visitor抽象访问者接口(ShapeVisitor)
ConcreteVisitor具体访问者(DrawVisitor、AreaVisitor 等)
Element元素接口(Shape),定义 accept 方法
ConcreteElement具体元素(Circle、Rectangle)
Object Structure对象结构(Canvas),管理元素集合
访问者模式 vs 其他模式对比
模式目的扩展方向典型场景
访问者在稳定结构上添加新操作添加新操作AST 处理、文档转换
组合构建树形结构添加新元素GUI 树、文件系统
策略替换算法替换行为支付、排序
命令封装请求添加新命令撤销、宏
Python 中的实用建议
  • 访问者模式在 Python 中使用较少,因为 Python 是动态语言,很多场景可以用:
    • 函数作为访问者(传入不同函数)
    • getattr(element, operation_name)()动态调用
    • 多分派库(如functools.singledispatchmultipledispatch

更 Pythonic 的替代方式

fromfunctoolsimportsingledispatch@singledispatchdefprocess_shape(shape):raiseNotImplementedError(f"Unsupported shape:{type(shape)}")@process_shape.registerdef_(shape:Circle):print(f"处理圆形: 半径{shape.radius}")@process_shape.registerdef_(shape:Rectangle):print(f"处理矩形: 宽高{shape.width}x{shape.height}")# 使用forshapeincanvas.shapes:process_shape(shape)
注意事项
  • 访问者模式违反了“依赖倒置原则”(高层依赖抽象),因为访问者需要知道所有具体元素类型
  • 添加新元素类型时,需要修改所有访问者(违反开闭原则)
  • 适合元素结构稳定、操作频繁变化的场景
  • 如果元素经常变化,考虑用组合 + 访问者双向结合
总结

访问者模式是处理稳定数据结构 + 多变操作的经典解决方案,在编译器、解释器、序列化等系统中非常常见。
但在 Python 中,由于语言的动态性,通常优先考虑更简单的方案(如 singledispatch、函数式编程)。

如果你想看更实际的例子(如 AST 遍历、HTML 渲染器、报表统计访问者),或者如何结合组合模式构建复杂结构,欢迎继续问!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 15:04:58

手把手实现STM32H7的UART DMA空闲接收

手把手教你用STM32H7实现UART DMA空闲中断接收&#xff1a;告别轮询&#xff0c;拥抱高效通信你有没有遇到过这样的场景&#xff1f;串口收数据时&#xff0c;每来一个字节就进一次中断&#xff0c;CPU被“打断”得喘不过气&#xff1b;Modbus协议帧长度不固定&#xff0c;靠超…

作者头像 李华
网站建设 2026/4/22 14:30:42

BongoCat终极使用指南:从新手到高手的快速上手指南

厌倦了单调的电脑操作体验&#xff1f;BongoCat正是为你的数字生活注入活力与趣味的完美解决方案。这款创新的桌面应用通过可爱的虚拟猫咪实时模拟你的输入动作&#xff0c;让每一次键盘敲击和游戏操作都变得生动有趣。无论你是程序员、作家还是游戏玩家&#xff0c;BongoCat都…

作者头像 李华
网站建设 2026/4/18 13:45:16

科研新视界:书匠策AI如何重构期刊论文写作生态

在学术研究的浩瀚星河中&#xff0c;期刊论文始终是研究者们展现智慧、传递思想的核心载体。然而&#xff0c;传统写作模式下的选题迷茫、文献梳理耗时、结构逻辑混乱等问题&#xff0c;如同无形的枷锁&#xff0c;束缚着研究者的创造力。如今&#xff0c;一款名为书匠策AI的科…

作者头像 李华
网站建设 2026/4/23 12:13:35

Dify如何平衡生成质量与token消耗成本?

Dify如何平衡生成质量与token消耗成本&#xff1f; 在大模型应用日益普及的今天&#xff0c;企业面临的不再是“能不能用AI”的问题&#xff0c;而是“如何用得又好又省”。一个看似简单的客服机器人&#xff0c;若设计不当&#xff0c;每月可能产生数百万甚至上千万的token消…

作者头像 李华
网站建设 2026/4/23 12:12:41

如何在Windows上快速部署Open-AutoGLM?这份保姆级教程让你一次成功

第一章&#xff1a;Windows上部署Open-AutoGLM的背景与意义 随着大语言模型技术的快速发展&#xff0c;本地化部署高性能推理模型成为企业与开发者关注的重点。Open-AutoGLM 作为一款开源的自动化生成语言模型&#xff0c;具备强大的文本理解与生成能力&#xff0c;支持在多种操…

作者头像 李华
网站建设 2026/4/23 12:24:25

6、生成手写数字的GAN与深度卷积GAN入门

生成手写数字的GAN与深度卷积GAN入门 1. GAN基础与训练算法 GAN由生成器(Generator)和判别器(Discriminator)两个网络组成,它们有着不同的目标。判别器试图最大化真阳性和真阴性分类,即最小化假阳性和假阴性分类;而生成器的目标是最大化判别器的假阳性分类,也就是让判…

作者头像 李华