Visitor pattern 使用 Multiple Dispatching。 如果只看 implementation 而不是 intent,很容易把两者混淆。
Visitor 假设你有一套不能修改的 primary class hierarchy。 也许它来自另一个 vendor,你碰不到 source。 但你又想给它增加新的 polymorphic method。 正常情况下,会在 base class interface 上加东西, 可现在这条路不可用。 怎么办?
Visitor 是 GoF Design Patterns 中最后一个 pattern, 专门解决这类问题。 它让你可以扩展 primary class hierarchy 的 interface。 前提是 primary class hierarchy 已经有一个 method, 通常叫 accept(), 它接收 secondary class hierarchy 中名为 Visitor 的 object。 针对 primary hierarchy 的 operation 因而变成 dynamically bound。 primary hierarchy 的 object accept() 这个 Visitor, 然后调用 Visitor 上 dynamically bound 的 method:
# flower_visitors.py
import random
from collections.abc import Iterator
from typing import Any, override
# The Flower hierarchy cannot be changed:
class Flower:
def accept(self, visitor: Any) -> None:
visitor.visit(self)
def pollinate(self, pollinator: Visitor) -> None:
print(self, "pollinated by", pollinator)
def eat(self, eater: Visitor) -> None:
print(self, "eaten by", eater)
def __str__(self) -> str:
return type(self).__name__
class Gladiolus(Flower):
pass
class Ranunculus(Flower):
pass
class Chrysanthemum(Flower):
@override
def eat(self, eater: Visitor) -> None:
print(self, "is toxic to", eater)
# The secondary hierarchy accepted by Flower:
class Visitor:
def __str__(self) -> str:
return type(self).__name__
class Bug(Visitor):
pass
# The middle layer names the operation:
class Pollinator(Bug):
def visit(self, flower: Flower) -> None:
flower.pollinate(self)
class Predator(Bug):
def visit(self, flower: Flower) -> None:
flower.eat(self)
# Concrete visitors, grouped by the operation they perform:
class Bee(Pollinator):
pass
class Fly(Pollinator):
pass
class Worm(Predator):
pass
def flower_gen(n: int) -> Iterator[Flower]:
flowers = Flower.__subclasses__()
for _ in range(n):
yield random.choice(flowers)()
# Now perform Bug operations on the flowers:
if __name__ == "__main__":
bee = Bee()
fly = Fly()
worm = Worm()
random.seed(47) # Reproducible flower sequence
for flower in flower_gen(4):
flower.accept(bee)
flower.accept(fly)
flower.accept(worm)
#: Ranunculus pollinated by Bee
#: Ranunculus pollinated by Fly
#: Ranunculus eaten by Worm
#: Gladiolus pollinated by Bee
#: Gladiolus pollinated by Fly
#: Gladiolus eaten by Worm
#: Ranunculus pollinated by Bee
#: Ranunculus pollinated by Fly
#: Ranunculus eaten by Worm
#: Chrysanthemum pollinated by Bee
#: Chrysanthemum pollinated by Fly
#: Chrysanthemum is toxic to Worm
flower_gen() 从 Flower.__subclasses__() 中抽取 concrete class, 这是 Factory 用过的 registry-free enumeration。 accept()/visit() 这一对就是 double dispatch。 accept() 把 concrete flower 交给 visitor, visit() resolve visitor 的 type, 然后 visit() 内调用 pollinate() 或 eat(), 再 resolve flower 的 type。 经典 pattern 中,每个 element class 都会 override accept(), 从而在第一步 resolve element type。 这里一个 inherited accept() 已经足够, 因为 flower type 会在下一步 resolve。 output 最后一行让两次 dispatch 都产生可见效果。 Chrysanthemum override 了 eat() (chrysanthemum 确实会产生天然 insecticide), 所以这一行同时依赖两个 unknown type: worm 的 type 决定调用 eat(), flower 的 type 决定到底运行哪个 eat()。 如果删掉 override,program 仍能运行; 只是 flower-side dispatch 再次无话可说。
上面的 output 展示 result,没有展示 mechanism。 把每一步真正抵达的 method qualified name 打印出来,就能看见这对 dispatch:
# dispatch_trace.py
from flower_visitors import Chrysanthemum, Gladiolus, Worm
worm = Worm()
for flower in (Chrysanthemum(), Gladiolus()):
print(type(worm).visit.__qualname__,
"then", type(flower).eat.__qualname__)
flower.accept(worm)
#: Predator.visit then Chrysanthemum.eat
#: Chrysanthemum is toxic to Worm
#: Predator.visit then Flower.eat
#: Gladiolus eaten by Worm
第一步到达 Predator.visit 而不是 Worm.visit, 因为 Worm 从定义该 operation 的 class 继承它。 对 Chrysanthemum,第二步抵达 override; 对 Gladiolus,第二步抵达 Flower.eat: flower-side dispatch 没有额外 behavior,而 output 现在把这一点显示出来。
flower_visitors.py 中有一个 annotation 看起来像 shortcut,其实不是。 accept() 把 visitor 标成 Any, 因为 Visitor base class 根本没有声明 visit() method: 如果 parameter 改成 Visitor,type checker 会失败。 经典 pattern 的修复方式,是在 visitor base 上把 visit() 声明为 abstract。 用 Protocol 只需要多两行和一个 import,就能去掉 Any:
class Visits(Protocol):
def visit(self, flower: Flower) -> None: ...
class Flower:
def accept(self, visitor: Visits) -> None:
visitor.visit(self)
本章保留 Any,是因为 empty Visitor base 正是经典 pattern 的样子, 而让你看到它的代价本身就是重点。 代价是 visitor side 完全没有 checking: Gladiolus().accept(Bug()) 能通过 type checker, 然后 runtime 才以 AttributeError: 'Bug' object has no attribute 'visit' 失败。 这和 32 · 多分派(Multiple Dispatching) 里 paper_scissors_rock.py 的 Any 留下的是同一个 gap。 这里的 Any 是 choice, 不像 22 · 数据传输对象(DTO) 中那种情况: attribute name 本来就是 runtime 才决定的 bag,根本没有 precise type 可写。
注意 behavior 放在哪里。 经典 pattern 会按 flower type overload visit(), 把每个 operation 的 body 放在 visitor 中; 因此只需要给 primary hierarchy 增加 accept()。 Python 没有 method overloading, 因为第二次 def visit() 会覆盖第一次。 所以这个 version 把 type-specific behavior 放回 flower 上的 pollinate() 与 eat(), visitor 只负责选择它们。 不管你怎么写, primary hierarchy 最终还是承载了 pattern 原本想让它远离的代码。
singledispatchPython 可以使用 functools.singledispatch, 从 hierarchy 外部给固定 hierarchy 添加 method-like operation。 它把一个 plain function 变成按第一个 argument type dispatch 的 function, 而 per-type implementation 可以从任意位置 register。 这正是 Visitor 想完成的事情, 却不需要 accept() method,也不需要 Visitor class hierarchy。 下面仍是同三个 flower。 两个 operation 都是独立新增的,互不依赖:
# visitor_singledispatch.py
from functools import singledispatch
class Flower:
def __str__(self) -> str:
return type(self).__name__
class Gladiolus(Flower):
pass
class Ranunculus(Flower):
pass
class Chrysanthemum(Flower):
pass
# A new operation, defined outside the Flower hierarchy:
@singledispatch
def nectar(flower: Flower) -> str:
return f"{flower}: no nectar"
@nectar.register
def _(flower: Gladiolus) -> str:
return f"{flower}: abundant nectar"
@nectar.register
def _(flower: Chrysanthemum) -> str:
return f"{flower}: a little nectar"
# A second operation, added independently of the first:
@singledispatch
def fragrance(flower: Flower) -> str:
return "faint"
@fragrance.register
def _(flower: Ranunculus) -> str:
return "strong"
if __name__ == "__main__":
flowers: list[Flower] = [
Gladiolus(), Ranunculus(), Chrysanthemum()]
for f in flowers:
print(nectar(f), "| fragrance:", fragrance(f))
print(sorted(t.__name__ for t in nectar.registry))
print(nectar.dispatch(Ranunculus)
is nectar.dispatch(Flower))
#: Gladiolus: abundant nectar | fragrance: faint
#: Ranunculus: no nectar | fragrance: strong
#: Chrysanthemum: a little nectar | fragrance: faint
#: ['Chrysanthemum', 'Gladiolus', 'object']
#: True
@nectar.register 会读取 implementation 第一个 parameter 的 annotation: flower: Gladiolus 会把这个 implementation 登记到 Gladiolus 下。 union annotation,例如 flower: Gladiolus | Ranunculus, 可以一次为多个 type register 同一个 implementation。 每个 registered implementation 都取名 _。 真正调用它的是 nectar() dispatcher,而不是这个 local name, 所以 name 本身没有意义。 _ 是“这个 name 不会被使用”的 conventional placeholder。 每个 registration 都重复使用 _ 是安全的: @nectar.register 会在下一个 def _ rebind name 之前, 先把当前 function 存进 dispatch table,所以不会丢任何东西。
完全不需要修改 Flower。 每个 operation 都是独立 function, @singledispatch default 处理任何没有 register 的 type。 dispatch 会沿 inheritance 查找: 未注册 subclass 使用最近的 registered ancestor, 只有没有 registered ancestor 时才 fallback 到 base implementation (下面 test 会固定这个 behavior)。