Python 如何实现访问者设计模式?什么是访问者(Visitor)模式?实际案例中有什么作用?

news2024/10/7 12:23:34

什么是访问者设计模式?访问者(Visitor)设计模式介绍:

访问者(Visitor)设计模式是一种行为设计模式,用于在不修改被访问对象的前提下定义新的操作。它通过将操作封装到独立的访问者类中,实现了将数据结构操作解耦的目标。这种模式适用于对复杂对象结构进行操作的场景,特别是当对象结构包含多个类型的对象,并且这些对象类型可能会发生变化。

在这里插入图片描述

主要角色:

  1. 访问者接口(Visitor): 定义了访问者类的接口,声明了可以访问哪些元素。

  2. 具体访问者类(ConcreteVisitor): 实现了访问者接口,提供了对每个元素进行操作的具体实现。

  3. 元素接口(Element): 定义了对象结构中的元素的接口,声明了接受访问者的方法。

  4. 具体元素类(ConcreteElement): 实现了元素接口,提供了接受访问者的具体实现。

  5. 对象结构类(ObjectStructure): 包含了多个元素,提供了一个接受访问者的方法,使访问者能够访问结构中的所有元素。

工作流程:

  1. 定义访问者接口,声明访问每种元素的方法。

  2. 实现具体访问者类,为每种元素提供具体的访问操作。

  3. 定义元素接口,声明接受访问者的方法。

  4. 实现具体元素类,提供接受访问者的具体实现。

  5. 定义对象结构类,包含多个元素,提供接受访问者的方法。

  6. 客户端创建访问者对象和对象结构对象,并通过对象结构的接受方法调用访问者的操作。

优点:

  • 分离关注点: 将数据结构与操作解耦,使得可以独立扩展两者之一而不影响另一方。

  • 新增操作方便: 可以轻松地新增对元素的操作,而无需修改元素本身。

  • 适用于复杂结构: 对于包含多个类型对象的复杂结构,访问者模式能更好地组织和管理操作。

Python 实现访问者模式示例代码(一):

from abc import ABC, abstractmethod

# 访问者接口
class Visitor(ABC):
    @abstractmethod
    def visit_element_a(self, element_a):
        pass

    @abstractmethod
    def visit_element_b(self, element_b):
        pass

# 具体访问者类
class ConcreteVisitor(Visitor):
    def visit_element_a(self, element_a):
        print(f"Visiting Element A: {element_a.operation_a()}")

    def visit_element_b(self, element_b):
        print(f"Visiting Element B: {element_b.operation_b()}")

# 元素接口
class Element(ABC):
    @abstractmethod
    def accept(self, visitor):
        pass

# 具体元素类
class ConcreteElementA(Element):
    def accept(self, visitor):
        visitor.visit_element_a(self)

    def operation_a(self):
        return "Operation A"

class ConcreteElementB(Element):
    def accept(self, visitor):
        visitor.visit_element_b(self)

    def operation_b(self):
        return "Operation B"

# 对象结构类
class ObjectStructure:
    def __init__(self):
        self.elements = []

    def add_element(self, element):
        self.elements.append(element)

    def accept(self, visitor):
        for element in self.elements:
            element.accept(visitor)

# 客户端
visitor = ConcreteVisitor()
element_a = ConcreteElementA()
element_b = ConcreteElementB()

object_structure = ObjectStructure()
object_structure.add_element(element_a)
object_structure.add_element(element_b)

object_structure.accept(visitor)

这个示例演示了访问者设计模式在 Python 中的应用。通过定义访问者接口、具体访问者类、元素接口、具体元素类和对象结构类,实现了对元素的访问操作。


Python 实现访问者模式示例代码(二)

假设我们有一个电商网站,有不同类型的商品,例如电子产品、服装和图书。我们希望实现一个价格计算器,根据不同类型的商品和用户等级计算最终价格。这个场景可以使用访问者设计模式。

from abc import ABC, abstractmethod

# 访问者接口
class PriceCalculatorVisitor(ABC):
    @abstractmethod
    def visit_electronic_product(self, electronic_product):
        pass

    @abstractmethod
    def visit_clothing(self, clothing):
        pass

    @abstractmethod
    def visit_book(self, book):
        pass

# 具体访问者类
class StandardPriceCalculator(PriceCalculatorVisitor):
    def visit_electronic_product(self, electronic_product):
        return electronic_product.base_price

    def visit_clothing(self, clothing):
        return clothing.base_price * 0.9  # 服装打九折

    def visit_book(self, book):
        return book.base_price * 0.8  # 图书打八折

# 元素接口
class Product(ABC):
    @abstractmethod
    def accept(self, visitor):
        pass

# 具体元素类
class ElectronicProduct(Product):
    def __init__(self, base_price):
        self.base_price = base_price

    def accept(self, visitor):
        return visitor.visit_electronic_product(self)

class Clothing(Product):
    def __init__(self, base_price):
        self.base_price = base_price

    def accept(self, visitor):
        return visitor.visit_clothing(self)

class Book(Product):
    def __init__(self, base_price):
        self.base_price = base_price

    def accept(self, visitor):
        return visitor.visit_book(self)

# 对象结构类
class ShoppingCart:
    def __init__(self):
        self.products = []

    def add_product(self, product):
        self.products.append(product)

    def calculate_total_price(self, visitor):
        total_price = 0
        for product in self.products:
            total_price += product.accept(visitor)
        return total_price

# 客户端
electronic_product = ElectronicProduct(base_price=100)
clothing = Clothing(base_price=50)
book = Book(base_price=30)

shopping_cart = ShoppingCart()
shopping_cart.add_product(electronic_product)
shopping_cart.add_product(clothing)
shopping_cart.add_product(book)

standard_price_calculator = StandardPriceCalculator()
total_price = shopping_cart.calculate_total_price(standard_price_calculator)

print(f"Total Price: ${total_price}")

在这个示例中,PriceCalculatorVisitor 是访问者接口,定义了不同类型商品的价格计算方法。StandardPriceCalculator 是具体访问者类,实现了实际的价格计算逻辑。Product 是元素接口,定义了接受访问者的方法。ElectronicProductClothingBook 是具体元素类,分别代表不同类型的商品。ShoppingCart 是对象结构类,包含了多个商品,提供了计算总价格的方法。

在客户端,我们创建了一些商品并放入购物车,然后使用访问者模式计算总价格。这个例子展示了访问者设计模式在实际开发中的应用,如何有效地对不同类型的对象进行操作。


使用访问者模式时,需要注意哪些问题?

使用访问者设计模式时,需要注意一些问题,以确保模式的有效实施和代码的可维护性:

  1. 新元素的添加: 如果系统中新增了新的元素类型,需要修改所有的具体访问者类,为新增元素类型添加相应的访问方法。这可能导致修改多个类,违反了开闭原则。

  2. 元素接口修改: 如果元素接口发生变化,所有的具体元素类都需要进行相应的修改。这可能导致修改多个类,破坏了系统的稳定性。

  3. 违反封装: 访问者模式在一定程度上打破了元素的封装性,因为具体访问者类需要访问元素的内部状态。这可能使得元素的内部结构对外部可见,降低了封装性。

  4. 复杂性: 访问者模式引入了多个接口和类,增加了系统的复杂性。对于简单的对象结构,使用访问者模式可能过于繁琐。

  5. 理解困难: 对于不熟悉访问者模式的开发人员,理解其运作机制可能需要一些时间。在团队中使用该模式时,确保团队成员对其有足够的了解是重要的。

  6. 性能开销: 访问者模式可能引入一定的性能开销,特别是当对象结构较大且变动频繁时。在性能敏感的应用中,需要仔细评估使用访问者模式的代价。

  7. 使用场景限制: 访问者模式更适合于对象结构相对稳定且具有多种不同类型元素的情况。如果对象结构变动频繁,或者元素类型较少,可能没有必要引入访问者模式。

虽然访问者模式有一些潜在的问题,但在某些场景下,它仍然是一个强大的设计模式,能够有效地处理复杂的对象结构。在使用时,需要仔细考虑系统的特定需求和约束。


本文就到这里了,感谢您的阅读 。别忘了点赞、收藏~ Thanks♪(・ω・)ノ 🍇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1206170.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7安装pandora

因为需要python3.7以上的环境所以下载minicanda安装脚本 1.下载地址 https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-py38_4.9.2-Linux-x86_64.sh把脚本上传到服务器 2,给.sh文件添加x执行权限 sudo chmod ux Miniconda3-py38_4.9.2-Linu…

【汇编】汇编语言的介绍

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、汇编是什么?二、为什么要学习汇编语言?三、学习汇编语言的好处四、安装汇编环境4.1 下载虚拟环境4.2 配置虚拟环境 总结 前言 计算…

瑞吉外卖01-实现管理端登录登出功能

开发前准备 准备数据表 结合页面原型创建数据库reggie,可以使用图形化界面或者MySQL命令运行SQL文件导入表结构(使用命令时sql文件不要放在中文目录中) 创建工程 创建一个SpringBoot的工程(勾选Spring Web,MySQL和MyBatis),配置pom.xml文件导入druid,…

本地化工具:Soluling Localization Crack

Soluling 是一个本地化工具,包含本地化项目所需的所有功能。Solling 使本地化变得非常容易。Soluling 是桌面应用程序和命令行工具的组合 。Solling支持100多种文件格式。通过 Soluling,您可以本地化桌面应用程序、移动应用程序、Web 应用程序、文档和在…

【赠书第4期】机器学习与人工智能实战:基于业务场景的工程应用

文章目录 前言 1 机器学习基础知识 2 人工智能基础知识 3 机器学习和人工智能的实战案例 4 总结 5 推荐图书 6 粉丝福利 前言 机器学习与人工智能是当前最热门的领域之一,也是未来发展的方向。随着科技的不断进步,越来越多的企业开始关注和投入机…

C语言精选练习题:(8)使用冒泡排序排序整形数组

每日一言 纵使天光终将熄灭,我们也要歌颂太阳。 --我来到这世上为的是看太阳–巴尔蒙特 题目 输入10个整数,然后使用冒泡排序对数组内容进行升序排序,然后打印出数组的内容 解题思路 创建一个数组用循环将10个整数存到数组中使用冒泡排序打…

Apache Airflow (六) :DAG catchup 参数设置

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹…

Android自定义控件:一款多特效的智能loadingView

先上效果图(如果感兴趣请看后面讲解): 1、登录效果展示 2、关注效果展示 1、【画圆角矩形】 画图首先是onDraw方法(我会把圆代码写上,一步一步剖析): 首先在view中定义个属性:priv…

Leetcode刷题详解——黄金矿工

1. 题目链接:1219. 黄金矿工 2. 题目描述: 你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格…

第 19 章 网络编程

网络可以使不同物理位置上的计算机达到资源共享和通信的目的,在Java中也提供了专门的网络开发程序包--java.net,以方便开发者进行网络程序的开发,本章将讲解TCP与UDP程序开发 19.1 网络编程简介 将地理位置不同的、具有独立功能的多台计算机…

Leetcode-110 平衡二叉树

递归实现 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

优选算法精品解析

1.双指针(前后/左右双指针) 1.1 283.移动零 快排双指针的核心算法 左边所有数 < tmp,右边所有数 > tmp,以tmp这个数为标准 1.2 1089.复习零 如果一对双指针从左向右不行,那么就从右向左,换一个方向 1.3 202.快乐数 双指针中的快慢指针: slow1,fast2 1.4 11.最多盛水的…

如何使用免费的 Vecteezy 旅行视频

网址&#xff1a;https://www.vecteezy.com/ Vecteezy 是一个提供免费和付费矢量图形、模板、视频和其他创意资源的网站。该网站拥有大量旅行视频&#xff0c;可用于各种目的&#xff0c;例如个人使用、商业用途或教育用途。 要下载 Vecteezy 的免费旅行视频&#xff0c;请按…

类和对象(4):Date类.运算符重载 1

一、赋值运算符重载 1.1 运算符重载 运算符重载是具有特殊函数名的函数&#xff0c;函数名字为&#xff1a;关键词operator需要重载的运算符符号。 不能重载C/C中未出现的符号&#xff0c;如&#xff1a;operator。重载操作符必须有一个类类型参数。不能改变用于内置类型运算…

详细推导MOSFET的跨导、小信号模型、输出阻抗、本征增益

目录 前言 什么是跨导 什么是小信号模型 什么是输入阻抗和输出阻抗 什么是MOS管的输出阻抗 什么是MOS管的本征增益 共源极放大电路的输入和输出阻抗 一些其它MOS拓扑电路的增益 负载为恒流源 负载为二极管 前言 相信很多人在学习集成电路领域的时候 都对MOS管的…

HTML设置标签栏的图标

添加此图标最简单的方法无需修改内容&#xff0c;只需按以下步骤操作即可&#xff1a; 1.准备一个 ico 格式的图标 2.将该图标命名为 favicon.ico 3.将图标文件置于index.html同级目录即可 为什么我的没有变化&#xff1f; 答曰&#xff1a;ShiftF5强制刷新一下网页就行了

C#,数值计算——多项式计算,Poly的计算方法与源程序

1 文本格式 using System; using System.Text; namespace Legalsoft.Truffer { /// <summary> /// operations on polynomials /// </summary> public class Poly { /// <summary> /// polynomial c[0]c[1]xc[2]x^2 ..…

西门子精智屏数据记录U盘插拔问题总结

西门子精智屏数据记录U盘插拔问题总结 注意: 数据记录过程中不允许带电插拔 U 盘! 数据记录的相关功能可参考以下链接中的内容: TIA博途wincc V16 如何进行变量周期归档?

Java 之集合框架的详细介绍

文章目录 总的介绍1. **Collection 接口**2. **List 接口**3. **Set 接口**4. **Map 接口**5. **HashMap、LinkedHashMap、TreeMap**6. **Queue 接口**7. **Deque 接口** ArrayList 类1. **创建 ArrayList&#xff1a;**2. **添加元素&#xff1a;**3. **插入元素&#xff1a;*…

centos利用find提权反弹shell

需要说明的是利用find命令进行提权的方式已经不存在了&#xff0c;因为Linux默认不会为find命令授予suid权限&#xff0c;这里只是刻意的制造出了一种存在提权的环境 首先我们先介绍一下find命令&#xff0c;find命令主要用来在Linux中查找文件使用&#xff0c;它可以进行最基础…