訪問者模式

<image>

訪問者模式（visitor pattern），是一種將演算法與對象結構分離的軟體設計模式。在傳統的單分派語言比如Smalltalk、Java和C++之中，訪問者模式被用來模擬雙分派。在支持多分派的語言如CLOS之中，訪問者模式就不再需要了。

## 概述

訪問者模式是一個由許多對象構成的對象結構，這些對象的類都擁有一個`accept`方法用來接受訪問者對象；訪問者是一個接口，它有一個`visit`方法，這個方法對訪問到的對象結構中不同類型的元素作出相應的動作；在對象結構的訪問過程中，`visit`方法遍歷整個對象結構，對每一個元素都實施`accept`方法，在每一個元素的`accept`方法中回調訪問者的`visit`方法，從而使訪問者得以處理對象結構的每一個元素。我們可以針對對象結構設計不同的實在的訪問者類來完成不同的操作。

## 結構

<image>

在上面的UML類圖中：

- `ElementA`類不直接實現新的運算，`ElementA`轉而實現一個分派運算`accept(visitor)`，它將請求分配（委託）給被接受的訪問者對象（`visitor.visitElementA(this)`）。`Visitor1`類實現這個運算（`visitElementA(e:ElementA)`）。
- `ElementB` 接著通過分派到`visitor.visitElementB(this)`實現`accept(visitor)`。`Visitor1`類實現這個運算（`visitElementB(e:ElementB)`）。

UML序列圖展示了運行時交互：

- `Client`對象遊歷一個對象結構中的元素（`ElementA,ElementB`）並調用`accept(visitor)`於每個元素之上。
- 首先，`Client`調用`accept(visitor)`於`ElementA`之上，它調用`visitElementA(this)`於被接受的`visitor`對象之上。這個元素自身（`this`）被傳遞給`visitor`，使得它可以訪問`ElementA`（調用`operationA()`）。
- 此後，`Client`調用`accept(visitor)`於`ElementB`之上，它調用`visitElementB(this)`於`visitor`之上，它訪問`ElementB`（調用`operationB()`）。

## 例子

### Java

<image>

Java的例子：

```Java
import java.util.List;

interface CarElement {
    void accept(CarElementVisitor visitor);
}

interface CarElementVisitor {
    void visit(Body body);
    void visit(Car car);
    void visit(Engine engine);
    void visit(Wheel wheel);
}

class Wheel implements CarElement {
    private final String name;
    public Wheel(final String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Body implements CarElement {
    @Override
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Engine implements CarElement {
    @Override
    public void accept(CarElementVisitor visitor) {
        visitor.visit(this);
    }
}

class Car implements CarElement {
    private final List<CarElement> elements;
    public Car() {
        this.elements = List.of(
            new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left"), new Wheel("back right"),
            new Body(), new Engine()
        );
    }
    @Override
    public void accept(CarElementVisitor visitor) {
        for (CarElement element : elements) {
            element.accept(visitor);
        }
        visitor.visit(this);
    }
}

class CarElementDoVisitor implements CarElementVisitor {
    @Override
    public void visit(Body body) {
        System.out.println("Moving my body");
    }
    @Override
    public void visit(Car car) {
        System.out.println("Starting my car");
    }
    @Override
    public void visit(Wheel wheel) {
        System.out.println("Kicking my " + wheel.getName() + " wheel");
    }
    @Override
    public void visit(Engine engine) {
        System.out.println("Starting my engine");
    }
}

class CarElementPrintVisitor implements CarElementVisitor {
    @Override
    public void visit(Body body) {
        System.out.println("Visiting body");
    }
    @Override
    public void visit(Car car) {
        System.out.println("Visiting car");
    }
    @Override
    public void visit(Engine engine) {
        System.out.println("Visiting engine");
    }
    @Override
    public void visit(Wheel wheel) {
        System.out.println("Visiting " + wheel.getName() + " wheel");
    }
}

public class VisitorDemo {
    public static void main(final String[] args) {
        Car car = new Car();
        car.accept(new CarElementPrintVisitor());
        car.accept(new CarElementDoVisitor());
    }
}
```

輸出：

```output
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
```

### Python

Python的例子：

```python
from abc import ABC, abstractmethod
from functools import singledispatchmethod

class Element():
    def accept(self, visitor):
        visitor.visit(self)

class Body(Element): pass

class Engine(Element): pass

class Wheel(Element):
    def __init__(self, name):
        self.name = name

class ObjectStructure(ABC):
    @abstractmethod
    def __init__(self):
        self.element_list = []
    def __call__(self, visitor):
        for element in self.element_list:
            element.accept(visitor)
        self.accept(visitor)

class Car(Element, ObjectStructure):
    def __init__(self):
        self.element_list = [
            Wheel("front left"), Wheel("front right"),
            Wheel("back left"), Wheel("back right"),
            Body(), Engine()]

class Visitor(ABC):
    @abstractmethod
    def visit(self, element): pass

class PrintVisitor(Visitor): 
    @singledispatchmethod
    def visit(self, element): pass

@PrintVisitor.visit.register
def _(self, body :Body):
    print(f"Visiting body.")
@PrintVisitor.visit.register
def _(self, car :Car):
    print(f"Visiting car.")
@PrintVisitor.visit.register
def _(self, wheel :Wheel):
    print(f"Visiting {wheel.name} wheel.")
@PrintVisitor.visit.register
def _(self, engine :Engine):
    print(f"Visiting engine.")

class DoVisitor(Visitor):
    @singledispatchmethod
    def visit(self, element): pass
    
@DoVisitor.visit.register
def _(self, body :Body):
    print(f"Moving my body.")
@DoVisitor.visit.register
def _(self, car :Car):
    print(f"Starting my car.")
@DoVisitor.visit.register
def _(self, wheel :Wheel):
    print(f"Kicking my {wheel.name} wheel.")
@DoVisitor.visit.register
def _(self, engine: Engine):
    print(f"Starting my engine.")
```

這個實現通過採用標準庫`functools`中的`singledispatchmethod`，將方法變換為單一分派泛化函數，動態的每個具體訪問者擴展方法上對應每個具體元素的方法。下面是其用例：

```pycon
>>> car = Car()
>>> car(PrintVisitor())
Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
>>> car(DoVisitor())
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.
```

下面的例子通過命名約定採用了反射式實現方式：

```python
from abc import ABC, abstractmethod

class Element():
    def accept(self, visitor):
        key = '_'+type(self).__name__+'__visit'
        if key in visitor.__dir__():
            visitor.__getattribute__(key)(self)

class Body(Element): pass

class Engine(Element): pass

class Wheel(Element):
    def __init__(self, name):
        self.name = name

class ObjectStructure(ABC):
    @abstractmethod
    def __init__(self):
        self.element_list = []
    def __call__(self, visitor):
        for element in self.element_list:
            element.accept(visitor)
        self.accept(visitor)

class Car(Element, ObjectStructure):
    def __init__(self):
        self.element_list = [
            Wheel("front left"), Wheel("front right"),
            Wheel("back left"), Wheel("back right"),
            Body(), Engine()]

class Visitor():
    @classmethod
    def register(cls, *visitor_list):
        nmlist = ['_'+x.__name__+'__visit' 
            for x in Element.__subclasses__()]
        for fn in visitor_list:
            if fn.__name__ in nmlist:
                type.__setattr__(cls, fn.__name__, fn)
                del globals()[fn.__name__]

class PrintVisitor(Visitor): pass
def _Body__visit(self, body):
    print(f"Visiting body.")
def _Car__visit(self, car):
    print(f"Visiting car.")
def _Wheel__visit(self, wheel):
    print(f"Visiting {wheel.name} wheel.")
def _Engine__visit(self, engine):
    print(f"Visiting engine.")
PrintVisitor.register(_Body__visit, _Car__visit,
    _Wheel__visit, _Engine__visit)

class DoVisitor(Visitor): pass
def _Body__visit(self, body):
    print(f"Moving my body.")
def _Car__visit(self, car):
    print(f"Starting my car.")
def _Wheel__visit(self, wheel):
    print(f"Kicking my {wheel.name} wheel.")
def _Engine__visit(self, engine):
    print(f"Starting my engine.")
DoVisitor.register(_Body__visit, _Car__visit,
    _Wheel__visit, _Engine__visit)
```

### C#

下面的C#例子，聲明了一個獨立的`ExpressionPrintingVisitor`類處理列印。如果想要介入一個新的具體訪問者，則需要創建一個實現`Visitor`接口的新類，並且提供一個`Visit`方法的新實現。現存的類（`Literal`和`Addition`）將保持不變。

```csharp
using System;

namespace Wikipedia;

public interface Visitor {
    void Visit(Literal literal);  
    void Visit(Addition addition);
}

public class ExpressionPrintingVisitor : Visitor {
    public void Visit(Literal literal) {
        Console.WriteLine(literal.Value);
    }

    public void Visit(Addition addition) {
        double leftValue = addition.Left.GetValue();
        double rightValue = addition.Right.GetValue();
        var sum = addition.GetValue();
        Console.WriteLine($"{leftValue} + {rightValue} = {sum}");
    }
}

public abstract class Expression {
    public abstract void Accept(Visitor v);
   
    public abstract double GetValue();
}

public class Literal : Expression {
    public Literal(double value) {
        this.Value = value;
    }

    public double Value { get; set; }

    public override void Accept(Visitor v) {
        v.Visit(this);
    }
 
    public override double GetValue() {
        return Value;
    }
}

public class Addition : Expression {
    public Addition(Expression left, Expression right) {
        Left = left;
        Right = right;
    }

    public Expression Left { get; set; }
    public Expression Right { get; set; }

    public override void Accept(Visitor v) {
        Left.Accept(v);
        Right.Accept(v);
        v.Visit(this);
    }
  
    public override double GetValue() {
        return Left.GetValue() + Right.GetValue();
    }
}

public static class Program {
    public static void Main(string[] args) {
        // Emulate 1 + 2 + 3
        var e = new Addition(
            new Addition(
                new Literal(1),
                new Literal(2)
            ),
            new Literal(3)
        );

        var printingVisitor = new ExpressionPrintingVisitor();
        e.Accept(printingVisitor);
        Console.ReadKey();
    }
}
```

## 參考條目

- 合成模式
- 橋接模式
- 多分派