Python 设计模式行为型访问者模式
作者:范桂飓 发布时间:2023-10-18 14:44:52
一、访问者模式(Visitor Pattern)
数据结构中保存着许多元素,当我们希望改变一种对元素的处理方式时,要避免重复的修改数据结构。那么就要求我们在实现代码时,将数据的处理进行分离,即:数据类只提供一个数据处理的接口,而该数据处理接口就被称之为访问者。那么,相同结构的数据面临不同的处理结果时,我们只需要创建不同的访问者。
访问者模式,指作用于一个对象结构体上的元素的操作。访问者可以使用户在不改变该结构体中的类的基础上定义一个新的操作。
优点:
使得在访问者类中针对复杂类结构中的某个类添加新方法较为容易,即:只需要简单地添加一个新的访问者方法即可。如果不采用访问者模式,这需要在每个类中添加一个新的方法。
访问者将相关的方法集中在一个具体的访问者类中,而其他相关的方法集中在另外一个具体的访问者类中。也就是说,访问者子类是按照方法的类型来分类的。
缺点:
增加一个具体的新
ConcreteElement
类比较困难。因为此时需要在每一个ConcreteVisitor
类中添加该ConcreteElement
类的访问方法。
二、应用场景
当一个对象的结构中,包含有多种类型的具有不同接口的对象,且用户要在这些对象上进行依赖于具体的类的运算时,需要用到访问者模式。这就是为什么访问者模式要针对每个被访问的子类都设计一个不同的接口的原因。事实上,如果每个被访问的子类都有相同的接口,包括构造方法、其他方法、参数都一致,则访问者类只需要设计一个访问方法,在该方法中含有一个用于区别不同的被访问的子类的参数即可。例如:可以使用被访问者基类作为参数类型。在对象的结构中包含有多种类型的有不同接口的对象时,各个不同的访问方法可能为访问所对应的类提供不同的参数类型。
当有多个不同的并且互不相关的运算将作用到这些对象上,而用户不希望这些运算混淆这些类时,可以使用访问者模式将相关的操作放到独立的类中,例如:为了实现每个结点类中的计算价格方法,可以将所有的计算价格方法放到一个 VisitPrice
类中。
在对象的数据类型很少改变,但是需要经常改变操作或者增加新的操作的情况下,可以使用访问者模式。反之,如果 Element
的子类经常改变结构,例如:需要增加一个新的税种,这就需要在访问者类中增加新的访问方法,因此,在这种情况下使用访问者模式代价较高,尽量不要使用访问者模式。
三、代码示例
该类图包含两个系列的类:“Element
类” 和 “访问者类”,访问者类定义了施加于 Element
类上的操作,为 Element 类提供一些功能。可以有多种具体的访问者类,各自完成特定的目的,如一个访问者类是计算价格,另一个访问者类则是计算存货数量。因此需要定义一个抽象的访问者父类 Visitor 以及用于各种特殊目的具体的子类。Visitor 类必须给每个结点类提供一个操作,即访问方法,例如获得各结点所代表的商品对象的价格等。
实体角色组成:
Visitor:为每个 Element 的对象声明一个访问操作。该访问操作的名字最好要包含被访问的类的名字,以便确认该访问操作是专门针对哪个具体的类,如:
visitFamilyNoChildren
是专门为了服务类 FamilyNoChildren 的。ConcreteVisitor:实现 Visitor 声明的运算。每个运算实现为对应的类的对象定义的算法的一部分。
ConcreteVisitor
提供算法的环境并且存储其局部状态。Element:定义了一些基本的方法,其中包含提供基本数据的方法,例如一些 get()与 set()方法。重要的是,每个 Element 子类都必须定义一个接收者方法,该方法以 Visitor 为参数类型:Accept(Visitor),其作用是为被访问者对象和访问者对象之间的交互提供接口。
ConcreteElement:具体的
Element
的子类,例如 ElementA,该类包含一个 accept 方法接收访问者对象。另外,该类还可能定义一些其他的方法以帮助访问者实现一些功能。ObjectStructure:提供一个高层接口,允许访问者访问 Element 的子类。在该类中可以包含一个结构,例如 ArrayList、Vector 等,提供所要访问的 element 的列表。
示例:上市公司的原始财务数据:
对于会计来说需要制作各种报表
对于财务总监来说需要分析公司业绩
对于战略顾问来说需要分析行业变化
class Finance:
"""财务数据结构类"""
def __init__(self):
self.salesvolume = None # 销售额
self.cost = None # 成本
self.history_salesvolume = None # 历史销售额
self.history_cost = None # 历史成本
def set_salesvolume(self, value):
self.salesvolume = value
def set_cost(self, value):
self.cost = value
def set_history_salesvolume(self, value):
self.history_salesvolume = value
def set_history_cost(self, value):
self.history_cost = value
def accept(self, visitor):
pass
class Finance_year(Finance):
"""2018 年财务数据类"""
def __init__(self, year):
Finance.__init__(self)
self.work = [] # 安排工作人员列表
self.year = year
def add_work(self, work):
self.work.append(work)
def accept(self):
for obj in self.work:
obj.visit(self)
class Accounting:
"""会计类"""
def __init__(self):
self.ID = "会计"
self.Duty = "计算报表"
def visit(self, table):
print('会计年度: {}'.format(table.year))
print("我的身份是: {} 职责: {}".format(self.ID, self.Duty))
print('本年度纯利润: {}'.format(table.salesvolume - table.cost))
print('------------------')
class Audit:
"""财务总监类"""
def __init__(self):
self.ID = "财务总监"
self.Duty = "分析业绩"
def visit(self, table):
print('会计总监年度: {}'.format(table.year))
print("我的身份是: {} 职责: {}".format(self.ID, self.Duty))
if table.salesvolume - table.cost > table.history_salesvolume - table.history_cost:
msg = "较同期上涨"
else:
msg = "较同期下跌"
print('本年度公司业绩: {}'.format(msg))
print('------------------')
class Adviser:
"""战略顾问"""
def __init__(self):
self.ID = "战略顾问"
self.Duty = "制定明年战略"
def visit(self, table):
print('战略顾问年度: {}'.format(table.year))
print("我的身份是: {} 职责: {}".format(self.ID, self.Duty))
if table.salesvolume > table.history_salesvolume:
msg = "行业上行,扩大生产规模"
else:
msg = "行业下行,减小生产规模"
print('本年度公司业绩: {}'.format(msg))
print('------------------')
class Work:
"""工作类"""
def __init__(self):
self.works = [] # 需要处理的年度数据列表
def add_work(self, obj):
self.works.append(obj)
def remove_work(self, obj):
self.works.remove(obj)
def visit(self):
for obj in self.works:
obj.accept()
if __name__ == '__main__':
work = Work() # 计划安排财务、总监、顾问对2018年数据处理
# 实例化2018年数据结构
finance_2018 = Finance_year(2018)
finance_2018.set_salesvolume(200)
finance_2018.set_cost(100)
finance_2018.set_history_salesvolume(180)
finance_2018.set_history_cost(90)
accounting = Accounting() # 实例化会计
audit = Audit() # 实例化总监
adviser = Adviser() # 实例化顾问
finance_2018.add_work(accounting) # 会计安排到2018分析日程中
finance_2018.add_work(audit) # 总监安排到2018分析日程中
finance_2018.add_work(adviser) # 顾问安排到2018分析日程中
work.add_work(finance_2018) # 添加2018年财务工作安排
work.visit()
来源:https://is-cloud.blog.csdn.net/article/details/122934106
猜你喜欢
- Oracle :NvlNVL函数:NVL函数是将NULL值的字段转换成默认字段输出。NVL(expr1,expr2)expr1,需要转换的字
- Q0.创建用户【前提】 你必须有CREATE USER系统权限。当你使用CREATE USER语句创建一
- MySQL目前不支持列的Default 为函数的形式,如达到你某列的默认值为当前更新日期与时间的功能,你可以使用TIMESTAMP列类型下面
- 论证完使用target=_blank并非绝对错误之后,分场景探讨如何减少新开窗口。自有意识注意这个问题,是看到蓝色经典Plod大叔在04年提
- 题目描述1266. 访问所有点的最小时间 - 力扣(LeetCode)平面上有 n 个点,点的位置用整数坐标表示 poi
- 引言除非您正在对服务进行原型设计,否则您可能会关心应用程序的内存使用情况。内存占用更小,基础设施成本降低,扩展变得更容易/延迟。尽管 Go
- MySQL是一个非常流行的小型关系型数据库管理系统,2008年1月16号被Sun公司收购。目前MySQL被广泛地应用在Internet上的中
- 现在很多地方都需要用到关键词过滤功能。比如一般的服务器都不允许一些词出现在网页上,站长有时候会对在本网站发布信息的内容进行一个广告过滤等。雨
- 基于smtplib包制作而成,但在实践中发现一个不知道算不算是smtplib留的一个坑,在网络断开的情况下发送邮件时会抛出一个socket.
- 根据国务院文件,5.19-5.21为全国哀悼日,在此期间,全国和各驻外机构下半旗志哀,停止公共娱乐活动,外交部和我国驻外使领馆设立吊唁簿。5
- 本文实例讲述了Python基于time模块求程序运行时间的方法。分享给大家供大家参考,具体如下:要记录程序的运行时间可以利用Unix系统中,
- <?php /* *文件名:linearList.php * 功能:数据结构线性表的顺序存储实现 * author:黎锦焕 * @co
- 前言责任链模式(Chain of Responsibility Pattern)是什么?责任链模式是一种行为型模式,它允许多个对象将请求沿着
- <?php //本功能主要是利用文件修改时间函数filemtime与现在时间作减法判断是否更新内容。 $cahetime=2;//设置
- 最近帮朋友做了点东西,最后需要将结果在网页中展示,这就需要搭建个服务器,做几个网页把数据信息展示出来。网上找了一下,阿里腾讯都有租服务器的业
- 一段非常简单代码普通调用方式def console1(a, b): print("进入函数")
- 代码如下:declare @cmd nvarchar(4000) set @cmd = N'exec [?].sys.sp_chan
- 由于 window.onload 事件需要在页面所有内容(包括图片等)加载完后,才执行,但往往我们更希望在 DOM 一加载完就执行脚本。其实
- 一般情况下,网站的图片代码是这样的。<img src="./images/test.jpg"
- MySQL从5.1开始支持event功能,类似oracle的job功能。有了这个功能之后我们就可以让MySQL自动的执行数据汇总等功能,不用