Python访问者模式
范桂飓 人气:0一、访问者模式(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()
加载全部内容