我一直在博客中看到访客模式的参考,但我不得不承认,我就是不明白。我读了维基百科上关于这个模式的文章,我理解了它的机制,但我仍然不知道什么时候使用它。

作为一个最近才真正了解装饰器模式的人,现在看到它在任何地方都有使用,我希望能够真正直观地理解这个看似方便的模式。


当前回答

感谢@Federico A. Ramponi的精彩解释,我只是在java版本中做了这个。希望对大家有所帮助。

正如@Konrad Rudolph指出的那样,这实际上是使用两个具体实例一起确定运行时方法的双重分派。

因此,实际上,只要正确定义了操作接口,就不需要为操作执行器创建公共接口。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

正如您所期望的那样,公共接口将为我们带来更多的清晰度,尽管它实际上并不是这个模式的基本部分。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

其他回答

Visitor设计模式非常适用于目录树、XML结构或文档概要等“递归”结构。

Visitor对象访问递归结构中的每个节点:每个目录、每个XML标记等等。Visitor对象不遍历结构。相反,Visitor方法应用于结构的每个节点。

这是一个典型的递归节点结构。可以是目录或XML标记。 [如果你是一个Java人,想象一下有很多额外的方法来构建和维护子列表。]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

visit方法将Visitor对象应用于结构中的每个节点。在本例中,它是一个自顶向下的访问者。您可以更改visit方法的结构,以进行自底向上或其他排序。

这里有一个供访问者使用的超类。它被visit方法所使用。它“到达”结构中的每个节点。由于visit方法调用了up和down,因此访问者可以跟踪深度。

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

子类可以做一些事情,比如在每个级别上计算节点并积累一个节点列表,生成一个良好的路径分层节号。

这是申请表。它构建了一个树结构,someTree。它创建了一个Visitor, dumpNodes。

然后它将dumpNodes应用到树中。dumpNode对象将“访问”树中的每个节点。

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

TreeNode访问算法将确保每个TreeNode都被用作Visitor的arrivedAt方法的参数。

我真的很喜欢http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html上的描述和例子。

The assumption is that you have a primary class hierarchy that is fixed; perhaps it’s from another vendor and you can’t make changes to that hierarchy. However, your intent is that you’d like to add new polymorphic methods to that hierarchy, which means that normally you’d have to add something to the base class interface. So the dilemma is that you need to add methods to the base class, but you can’t touch the base class. How do you get around this? The design pattern that solves this kind of problem is called a “visitor” (the final one in the Design Patterns book), and it builds on the double dispatching scheme shown in the last section. The visitor pattern allows you to extend the interface of the primary type by creating a separate class hierarchy of type Visitor to virtualize the operations performed upon the primary type. The objects of the primary type simply “accept” the visitor, then call the visitor’s dynamically-bound member function.

感谢@Federico A. Ramponi的精彩解释,我只是在java版本中做了这个。希望对大家有所帮助。

正如@Konrad Rudolph指出的那样,这实际上是使用两个具体实例一起确定运行时方法的双重分派。

因此,实际上,只要正确定义了操作接口,就不需要为操作执行器创建公共接口。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

正如您所期望的那样,公共接口将为我们带来更多的清晰度,尽管它实际上并不是这个模式的基本部分。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

我不理解这种模式,直到我看到bob叔叔的文章和评论。 考虑下面的代码:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

虽然它看起来很好,因为它确认了单一责任,但它违反了开放/封闭原则。每次你有新的员工类型,你将不得不添加如果与类型检查。如果你不知道,在编译时你永远也不会知道。

使用访问者模式,你可以让你的代码更干净,因为它不违反开放/关闭原则,也不违反单一责任。如果你忘记实现visit,它将不会编译:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

神奇的是,虽然v.Visit(this)看起来一样,但实际上是不同的,因为它调用不同的访问者重载。

我不太熟悉来客模式。看看我做得对不对。假设你有一个动物等级

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(假设它是一个具有良好接口的复杂层次结构。)

现在我们想要向层次结构添加一个新操作,即我们想要每个动物发出它的声音。既然层次结构这么简单,你可以直接用多态性来实现:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

但是按照这种方式进行,每次想要添加操作时,都必须修改到层次结构中每个类的接口。现在,假设您对原始界面感到满意,并且希望对其进行尽可能少的修改。

访问者模式允许您在合适的类中移动每个新操作,并且您只需要扩展层次结构的接口一次。我们开始吧。首先,我们定义了一个抽象操作(GoF中的“Visitor”类),它对层次结构中的每个类都有一个方法:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

然后,我们修改层次结构以接受新的操作:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

最后,我们实现了实际的操作,没有修改Cat和Dog:

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

现在,您可以在不修改层次结构的情况下添加操作。 下面是它的工作原理:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}