我在网上最常遇到的用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 装饰器不能直接与实例方法一起使用,尽管there are ways to make it work。 注意:从 Python 3.8 开始,functools.singledispatchmethod() 提供与 functools.singledispatch() 相同的功能,但用于实例方法。
对于 v3.4 之前的 Python,multimethods 模块可以类似于 singledispatch 装饰器使用。 multimethods 模块的一个缺点是,应用于给定数据结构元素的访问者方法的选择不仅基于元素的类型,而且还基于声明方法的顺序。对于具有复杂继承层次结构的数据结构,将方法定义保持在正确的顺序可能很麻烦且容易出错。