前言
GOF设计模式分三大类:
- 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。
- 结构型模式:关注类和对象之间的组合,包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。
- 行为型模式:关注对象之间的交互,包括职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
一、访问者模式
访问者模式(Visitor Pattern)
定义:提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
解决问题:如何操作一个包含多种类型对象的复杂结构?
使用场景:
- 当系统中存在一个较为复杂的对象结构,且不同访问者对其所采取的操作也不相同时,可以考虑使用访问者模式进行设计。
- 一个对象结构包含多种类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
组成:
- Visitor(抽象访问者):抽象访问者为对象结构中每个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型。
- ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每个操作用于访问对象结构中一种类型的元素。
- Element(抽象元素):定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
- ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
- ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。
补充说明:
- 访问者模式,包含访问者和被访问元素两个主要组成部分。
- 这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。
- 被访问的元素通常存储在一个集合中,这个集合称为“对象结构”。访问者通过遍历对象结构实现对其中存储的元素的逐个操作。
- 访问者模式包括两个层次结构:一个是访问者层次结构,提供了抽象访问者和具体访问者;另一个是元素层次结构,提供了抽象元素和具体元素。
优点:
- 增加新的访问操作很方便。
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰。
缺点:
- 增加新的元素类很困难。
- 破坏封装。
二、访问者模式示例
使用访问者模式,设计系统中员工数据汇总模块
- 员工包括正式员工和临时工,每周人力资源部和财务部等部门需要对员工数据进行汇总。人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
- 正式员工每周工作时间为40小时。不同级别、不同部门的员工每周基本工资不同。如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假所扣工资以80元/小时计算,直到基本工资扣除到零为止。
- 临时工每周工作时间不固定。基本工资按小时计算,不同岗位的临时工小时工资不同。
- FADepartment表示财务部,HRDepartment表示人力资源部,它们充当具体访问者角色,其抽象父类Department充当抽象访问者角色;
- EmployeeList充当对象结构,用于存储员工列表;
- FulltimeEmployee表示正式员工,ParttimeEmployee表示临时工,它们充当具体元素角色,其父接口Employee充当抽象元素角色。
"""访问者模式"""
### 抽象访问者
class Departement:
"""部门"""
def visit_fulltime_employee(self, employee: "FulltimeEmployee"):
raise NotImplementedError
def visit_parttime_employee(self, employee: "ParttimeEmployee"):
raise NotImplementedError
### 具体访问者
class Finance(Departement):
"""财务部"""
def visit_fulltime_employee(self, employee):
"""实现财务部对全职员工的访问"""
if employee.work_time > 40:
employee.weekly_wage = (
employee.weekly_wage + (employee.work_time - 40) * 100
)
else:
employee.weekly_wage = (
employee.weekly_wage - (40 - employee.work_time) * 100
)
if employee.weekly_wage < 0:
employee.weekly_wage = 0
print(f"正式员工:{employee.name},实际工资为:{employee.weekly_wage}")
def visit_parttime_employee(self, employee):
"""实现人事部对兼职员工的访问"""
print(
f"兼职员工:{employee.name},实际工资为:{employee.work_time*employee.hourly_wage}"
)
class HR(Departement):
"""人事部"""
def visit_fulltime_employee(self, employee):
"""实现人事部对全职员工的访问"""
print(f"正式员工:{employee.name},实际工作时间:{employee.work_time}")
if employee.work_time > 40:
print(f"正式员工:{employee.name},加班时间为:{employee.work_time - 40}")
else:
print(f"正式员工:{employee.name},请假时间为:{40-employee.work_time}")
def visit_parttime_employee(self, employee):
"""实现人事部对兼职员工的访问"""
print(f"兼职员工:{employee.name},实际工作时间:{employee.work_time}")
### 抽象元素
class Employee:
"""员工"""
def accept(self, handler: Departement):
"""接受一个抽象访问者访问"""
raise NotImplementedError
### 具体元素
class FulltimeEmployee(Employee):
"""全职员工"""
def __init__(self, name: str, weekly_wage: float, work_time: int):
self.name = name # 员工姓名
self.weekly_wage = weekly_wage # 员工周薪
self.work_time = work_time # 工作时间
def accept(self, handler):
handler.visit_fulltime_employee(self) # 调用访问者的访问方法
class ParttimeEmployee(Employee):
"""兼职员工"""
def __init__(self, name: str, hourly_wage: float, work_time: int):
self.name = name # 员工姓名
self.hourly_wage = hourly_wage # 员工时薪
self.work_time = work_time # 工作时间
def accept(self, handler):
handler.visit_parttime_employee(self) # 调用访问者的访问方法
### 对象结构
class EmployeeList:
"""员工列表"""
def __init__(self):
self.employees = []
def add_employee(self, employee):
self.employees.append(employee)
def accept(self, departement):
for employee in self.employees:
employee.accept(departement)
def __str__(self):
return "\n".join([str(employee) for employee in self.employees])
- 客户端代码
### 客户端代码
if __name__ == "__main__":
employee_list = EmployeeList()
employee_list.add_employee(FulltimeEmployee("张三", 5000, 45))
employee_list.add_employee(FulltimeEmployee("李四", 6000, 40))
employee_list.add_employee(FulltimeEmployee("王五", 7000, 38))
employee_list.add_employee(ParttimeEmployee("赵六", 80, 20))
employee_list.add_employee(ParttimeEmployee("孙七", 60, 18))
dep: Departement = Finance()
# 如果需要更换具体访问者类,无须修改源代码,只需修改配置文件。
# dep: Departement = HR()
employee_list.accept(dep)
- 输出结果
正式员工:张三,实际工资为:5500
正式员工:李四,实际工资为:6000
正式员工:王五,实际工资为:6800
兼职员工:赵六,实际工资为:1600
兼职员工:孙七,实际工资为:1080
- 当访问者更换为HR时,输出结果
正式员工:张三,实际工作时间:45
正式员工:张三,加班时间为:5
正式员工:李四,实际工作时间:40
正式员工:李四,请假时间为:0
正式员工:王五,实际工作时间:38
正式员工:王五,请假时间为:2
兼职员工:赵六,实际工作时间:20
兼职员工:孙七,实际工作时间:18
您正在阅读的是《设计模式Python版》专栏!关注不迷路~