8.21 实现访问者模式

    这里遇到的问题在编程领域中是很普遍的,有时候会构建一个由大量不同对象组成的数据结构。假设你要写一个表示数学表达式的程序,那么你可能需要定义如下的类:

    然后利用这些类构建嵌套数据结构,如下所示:

    1. # Representation of 1 + 2 * (3 - 4) / 5
    2. t1 = Sub(Number(3), Number(4))
    3. t2 = Mul(Number(2), t1)
    4. t3 = Div(t2, Number(5))
    5. t4 = Add(Number(1), t3)

    这样做的问题是对于每个表达式,每次都要重新定义一遍,有没有一种更通用的方式让它支持所有的数字和操作符呢。这里我们使用访问者模式可以达到这样的目的:

    1. class NodeVisitor:
    2. def visit(self, node):
    3. methname = 'visit_' + type(node).__name__
    4. meth = getattr(self, methname, None)
    5. if meth is None:
    6. meth = self.generic_visit
    7. return meth(node)
    8.  
    9. def generic_visit(self, node):
    10. raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))

    为了使用这个类,可以定义一个类继承它并且实现各种 方法,其中Name是node类型。例如,如果你想求表达式的值,可以这样写:

    1. >>> e = Evaluator()
    2. >>> e.visit(t4)
    3. 0.6
    4. >>>

    作为一个不同的例子,下面定义一个类在一个栈上面将一个表达式转换成多个操作序列:

    1. def generate_code(self, node):
    2. self.instructions = []
    3. self.visit(node)
    4. return self.instructions
    5.  
    6. def visit_Number(self, node):
    7. self.instructions.append(('PUSH', node.value))
    8.  
    9. def binop(self, node, instruction):
    10. self.visit(node.left)
    11. self.visit(node.right)
    12. self.instructions.append((instruction,))
    13.  
    14. def visit_Add(self, node):
    15. self.binop(node, 'ADD')
    16.  
    17. def visit_Sub(self, node):
    18. self.binop(node, 'SUB')
    19.  
    20. def visit_Mul(self, node):
    21. self.binop(node, 'MUL')
    22.  
    23. self.binop(node, 'DIV')
    24.  
    25. def unaryop(self, node, instruction):
    26. self.visit(node.operand)
    27. self.instructions.append((instruction,))
    28.  
    29. def visit_Negate(self, node):
    30. self.unaryop(node, 'NEG')

    使用示例:

    刚开始的时候你可能会写大量的if/else语句来实现,这里访问者模式的好处就是通过 来获取相应的方法,并利用递归来遍历所有的节点:

    1. def binop(self, node, instruction):
    2. self.visit(node.left)
    3. self.visit(node.right)
    4. self.instructions.append((instruction,))

    还有一点需要指出的是,这种技术也是实现其他语言中switch或case语句的方式。比如,如果你正在写一个HTTP框架,你可能会写这样一个请求分发的控制器:

    1. class HTTPHandler:
    2. def handle(self, request):
    3. methname = 'do_' + request.request_method
    4. getattr(self, methname)(request)
    5. def do_GET(self, request):
    6. pass
    7. def do_POST(self, request):
    8. pass
    9. def do_HEAD(self, request):
    10. pass

    可以参照8.22小节,利用生成器或迭代器来实现非递归遍历算法。

    在跟解析和编译相关的编程中使用访问者模式是非常常见的。Python本身的 模块值的关注下,可以去看看源码。9.24小节演示了一个利用 模块来处理Python源代码的例子。

    原文: