公众号:人生只不过是一场投资
温馨提示:Python 模式等笔记文档绑定了笔记资源,手机端可能看不到,网页端有需要的请自行下载。
引言
在软件开发中,设计模式是一套被反复使用、经过分类和总结的代码设计经验。被广泛用于解决常见的问题。在 Python 脚本设计中,创建对象的方式多种多样,设计模式提供了多种有效的解决方案。访问者模式(Visitor Pattern)是一种行为型设计模式,旨在不改变被访问对象结构的情况下,定义对其元素的新操作。访问者模式通过将操作封装在独立的类中,使得新的操作可以灵活地添加,而无需修改对象的结构。访问者模式特别适用于结构相对稳定,但经常需要对结构中的元素进行操作的系统,如编译器、文档处理系统和图形系统。
应用领域
访问者模式在以下几种场景中有广泛的应用:
- 对象结构稳定:在对象结构相对稳定,但需要对对象进行不同操作的情况下,访问者模式可以方便地增加新操作。
- 需要对复杂对象进行操作:当需要对一个复杂对象结构(如对象树)进行操作时,访问者模式可以清晰地定义和组织这些操作。
- 分离无关功能:需要将无关的操作分离到不同的类中,避免类的职责过重。
- 跨类的操作:在不同行为跨越多个类的情况下,访问者模式可以将这些行为集中在一个访问者类中,简化管理。
- 数据解析与转换:在编译器、解释器等领域,访问者模式常用于遍历和处理语法树。
示例一
以下是一个Python实现访问者模式的示例,展示如何在不改变对象结构的情况下定义新的操作:
from abc import ABC, abstractmethod
# 元素接口:Element 类定义了一个接受访问者的方法 accept ,这是所有具体元素必须实现的方法。
class Element(ABC):
@abstractmethod
def accept(self, visitor):
pass
# 具体元素A:ConcreteElementA 和 ConcreteElementB 分别实现了 Element 接口,并在 accept 方法中调用访问者的对应方法。
class ConcreteElementA(Element):
def accept(self, visitor):
visitor.visit_concrete_element_a(self)
def operation_a(self):
return "ConcreteElementA"
# 具体元素B
class ConcreteElementB(Element):
def accept(self, visitor):
visitor.visit_concrete_element_b(self)
def operation_b(self):
return "ConcreteElementB"
# 访问者接口:Visitor 类定义了访问具体元素的方法 visit_concrete_element_a 和 visit_concrete_element_b 。
class Visitor(ABC):
@abstractmethod
def visit_concrete_element_a(self, element):
pass
@abstractmethod
def visit_concrete_element_b(self, element):
pass
# 具体访问者1:ConcreteVisitor1 和 ConcreteVisitor2 分别实现了访问者接口的方法,定义了对具体元素的操作。
class ConcreteVisitor1(Visitor):
def visit_concrete_element_a(self, element):
print(f"{element.operation_a()} visited by ConcreteVisitor1")
def visit_concrete_element_b(self, element):
print(f"{element.operation_b()} visited by ConcreteVisitor1")
# 具体访问者2
class ConcreteVisitor2(Visitor):
def visit_concrete_element_a(self, element):
print(f"{element.operation_a()} visited by ConcreteVisitor2")
def visit_concrete_element_b(self, element):
print(f"{element.operation_b()} visited by ConcreteVisitor2")
# 客户端代码: client_code 函数遍历元素列表,并对每个元素调用 accept 方法,以实现访问者对元素的操作。
def client_code(elements, visitor):
for element in elements:
element.accept(visitor)
# 测试访问者模式:创建元素和访问者对象,验证不同访问者对同一元素的不同操作。
elements = [ConcreteElementA(), ConcreteElementB()]
visitor1 = ConcreteVisitor1()
visitor2 = ConcreteVisitor2()
print("Visitor1:")
client_code(elements, visitor1)
print("\nVisitor2:")
client_code(elements, visitor2)
示例二
我们以一个文件系统为例,演示访问者模式的实现。该系统包含文件和目录,可以对它们执行不同的操作,如计算大小和显示信息。
from abc import ABC, abstractmethod
# 首先,定义一个元素接口 Element ,它包含接受访问者的方法:
# - 使用 `ABC` 和 `abstractmethod` 定义了一个抽象类 `Element`,要求具体元素实现 `accept` 方法。
class Element(ABC):
@abstractmethod
def accept(self, visitor):
pass
# 然后,定义具体的文件和目录类:
# - `File` 类表示文件,包含名称和大小,并实现了 `accept` 方法。
class File(Element):
def __init__(self, name, size):
self.name = name
self.size = size
def accept(self, visitor):
visitor.visit_file(self)
# - `Directory` 类表示目录,包含名称和子元素列表,并实现了 `accept` 方法。
class Directory(Element):
def __init__(self, name):
self.name = name
self.children = []
def add(self, element):
self.children.append(element)
def accept(self, visitor):
visitor.visit_directory(self)
# 定义访问者接口 `Visitor`,它包含访问文件和目录的方法:
# - 使用 `ABC` 和 `abstractmethod` 定义了一个抽象类 `Visitor`,要求具体访问者实现 `visit_file` 和 `visit_directory` 方法。
class Visitor(ABC):
@abstractmethod
def visit_file(self, file):
pass
@abstractmethod
def visit_directory(self, directory):
pass
# 然后,定义具体的访问者类:
# - `SizeVisitor` 计算文件系统的总大小,实现了 `visit_file` 和 `visit_directory` 方法。
class SizeVisitor(Visitor):
def __init__(self):
self.total_size = 0
def visit_file(self, file):
self.total_size += file.size
def visit_directory(self, directory):
for child in directory.children:
child.accept(self)
# - `InfoVisitor` 显示文件和目录的信息,实现了 `visit_file` 和 `visit_directory` 方法。
class InfoVisitor(Visitor):
def visit_file(self, file):
print(f"File: {file.name}, Size: {file.size}")
def visit_directory(self, directory):
print(f"Directory: {directory.name}")
for child in directory.children:
child.accept(self)
# 通过具体的元素和访问者类实现文件系统的操作:
# - 创建文件和目录
file1 = File("file1.txt", 100)
file2 = File("file2.txt", 200)
dir1 = Directory("dir1")
dir2 = Directory("dir2")
# - 构建目录结构
dir1.add(file1)
dir2.add(file2)
dir1.add(dir2)
# - 创建访问者
size_visitor = SizeVisitor()
info_visitor = InfoVisitor()
# - 访问文件和目录
dir1.accept(size_visitor)
dir1.accept(info_visitor)
print(f"Total Size: {size_visitor.total_size}")
# 运行结果
Directory: dir1
File: file1.txt, Size: 100
Directory: dir2
File: file2.txt, Size: 200
Total Size: 300
示例三
我们以一个编译器为例,演示访问者模式的实现。该系统包含不同类型的语法节点,可以对它们执行不同的操作,如生成代码和计算表达式的值。
from abc import ABC, abstractmethod
# 首先,定义一个元素接口 `Node`,它包含接受访问者的方法:
# - 使用 `ABC` 和 `abstractmethod` 定义了一个抽象类 `Node`,要求具体节点实现 `accept` 方法。
class Node(ABC):
@abstractmethod
def accept(self, visitor):
pass
# 然后,定义具体的语法节点类:
# - `NumberNode` 类表示数字节点,包含值,并实现了 `accept` 方法。
class NumberNode(Node):
def __init__(self, value):
self.value = value
def accept(self, visitor):
return visitor.visit_number(self)
# # - `AddNode` 类表示加法节点,包含左右子节点,并实现了 `accept` 方法。
class AddNode(Node):
def __init__(self, left, right):
self.left = left
self.right = right
def accept(self, visitor):
return visitor.visit_add(self)
# 定义访问者接口 `Visitor`,它包含访问不同节点的方法:
# - 使用 `ABC` 和 `abstractmethod` 定义了一个抽象类 `Visitor`,
# - 要求具体访问者实现 `visit_number` 和 `visit_add` 方法。
class Visitor(ABC):
@abstractmethod
def visit_number(self, number_node):
pass
@abstractmethod
def visit_add(self, add_node):
pass
# 然后,定义具体的访问者类:
# - `CodeGenVisitor` 生成表达式的代码,实现了 `visit_number` 和 `visit_add` 方法。
class CodeGenVisitor(Visitor):
def __init__(self):
self.code = ""
def visit_number(self, number_node):
self.code += str(number_node.value)
def visit_add(self, add_node):
self.code += "("
add_node.left.accept(self)
self.code += " + "
add_node.right.accept(self)
self.code += ")"
# - `EvalVisitor` 计算表达式的值,实现了 `visit_number` 和 `visit_add` 方法。
class EvalVisitor(Visitor):
def visit_number(self, number_node):
return number_node.value
# 正确地返回左右子节点计算的结果的和。
def visit_add(self, add_node):
left_value = add_node.left.accept(self)
right_value = add_node.right.accept(self)
return left_value + right_value
# 创建语法节点
num1 = NumberNode(1)
num2 = NumberNode(2)
add_node = AddNode(num1, num2)
# 创建访问者
code_gen_visitor = CodeGenVisitor()
eval_visitor = EvalVisitor()
# 访问语法节点
add_node.accept(code_gen_visitor)
print(f"Generated Code: {code_gen_visitor.code}")
result = add_node.accept(eval_visitor)
print(f"Evaluation Result: {result}")
# 运行结果:
Generated Code: (1 + 2)
Evaluation Result: 3
优点
- 增加新操作方便:访问者模式使得增加新操作变得简单,只需添加新的访问者类,而无需修改现有元素类的代码。
- 分离无关行为或功能:可以将不同的操作逻辑分离到不同的访问者类中,避免类的职责过重,使得代码更加清晰和易于管理。
- 增强可扩展性:可以通过增加新的访问者来扩展系统的功能,而无需修改现有代码,可以方便地添加新的访问者来实现新的操作。
- 简化复杂对象结构的操作:访问者模式适用于操作复杂对象结构(如对象树),可以清晰地定义和组织这些操作。
缺点
- 违反单一职责原则:元素类必须包含接受访问者的方法,可能会违反单一职责原则。
- 难以增加新元素:访问者模式对增加新元素不友好,如果对象结构发生变化,访问者和被访问类都需要修改。
- 对象结构公开:访问者模式要求对象结构对访问者公开,可能会破坏对象的封装性。
结论
访问者模式在不改变被访问对象结构的情况下定义新操作方面具有显著的优势,尤其在对象结构稳定、需要对复杂对象进行操作和跨类的操作等场景中表现出色。尽管访问者模式存在违反单一职责原则和难以增加新元素等缺点,但其带来的灵活性和可扩展性使其在实际开发中非常有价值。在实际应用中,应根据具体需求权衡利弊,合理使用访问者模式,以充分发挥其优势,避免其不足对系统造成影响。通过合适的设计和实现,访问者模式在Python应用中可以有效提高系统的灵活性和可维护性。