我在网上最常遇到的用 Python 实现访问者模式的两种变体:
- Gamma 等人的 Design Patterns 书中示例的一对一翻译。
- 使用附加模块进行双重调度
设计模式书的翻译示例
此变体使用accept()
数据结构类中的方法和visit_Type()
访问者中的相应方法。
数据结构
class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
def accept(self, visitor):
visitor.visitOperation(self)
class Integer(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitInteger(self)
class Float(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitFloat(self)
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
中缀印刷访客
class InfixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
operation.arg1.accept(self)
self.expression_string += ' ' + operation.op + ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
前缀打印访客
class PrefixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
self.expression_string += operation.op + ' '
operation.arg1.accept(self)
self.expression_string += ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
测试
infixPrintVisitor = InfixPrintVisitor()
expression.accept(infixPrintVisitor)
print(infixPrintVisitor.expression_string)
prefixPrintVisitor = PrefixPrintVisitor()
expression.accept(prefixPrintVisitor)
print(prefixPrintVisitor.expression_string)
输出
5 + 2 * 444.1
+ 5 * 2 444.1
使用附加模块
此变体使用@functools.singledispatch()
装饰器(自 Python v3.4 起在 Python 标准库中可用)。
数据结构
class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
class Integer(object):
def __init__(self, num):
self.num = num
class Float(object):
def __init__(self, num):
self.num = num
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
中缀印刷访客
from functools import singledispatch
@singledispatch
def visitor_print_infix(obj):
pass
@visitor_print_infix.register(Operation)
def __(operation):
return visitor_print_infix(operation.arg1) + ' ' \
+ operation.op + ' ' \
+ visitor_print_infix(operation.arg2)
@visitor_print_infix.register(Integer)
@visitor_print_infix.register(Float)
def __(number):
return number.num
前缀打印访客
from functools import singledispatch
@singledispatch
def visitor_print_prefix(obj):
pass
@visitor_print_prefix.register(Operation)
def __(operation):
return operation.op + ' ' \
+ visitor_print_prefix(operation.arg1) + ' ' \
+ visitor_print_prefix(operation.arg2)
@visitor_print_prefix.register(Integer)
@visitor_print_prefix.register(Float)
def __(number):
return number.num
测试
print(visitor_print_infix(expression))
print(visitor_print_prefix(expression))
输出
5 + 2 * 444.1
+ 5 * 2 444.1
我更喜欢这个变体的原因是它消除了accept()
方法并将数据结构与访问者中实现的操作完全分离。使用新元素扩展数据结构不需要更改访问者。访问者默认忽略未知元素类型(参见pass
关键字定义)。这种方法的一个缺点是singledispatch
装饰器不能直接与实例方法一起使用,尽管有一些方法可以使它工作。注意:从 Python 3.8 开始functools.singledispatchmethod()
提供与实例方法相同的功能functools.singledispatch()
。
对于 v3.4 之前的 Python,可以使用类似于 singledispatch 装饰器的多方法模块。multimethods 模块的一个缺点是,应用于给定数据结构元素的访问者方法的选择不仅基于元素的类型,而且还基于声明方法的顺序。对于具有复杂继承层次结构的数据结构,以正确的顺序保持方法定义可能很麻烦且容易出错。