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

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


当前回答

在我看来,使用访问者模式或直接修改每个元素结构添加新操作的工作量大致相同。此外,如果我要添加新的元素类,比如Cow,操作接口将受到影响,并且这将传播到所有现有的元素类,因此需要重新编译所有元素类。那么重点是什么呢?

其他回答

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方法的参数。

使用访问者模式至少有三个很好的理由:

减少代码的增殖,当数据结构发生变化时,代码只会略有不同。 将相同的计算应用于多个数据结构,而不改变实现计算的代码。 在不更改遗留代码的情况下向遗留库添加信息。

请看看我写的一篇关于这方面的文章。

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

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);
}

我发现下面的链接更容易:

在 http://www.remondo.net/visitor-pattern-example-csharp/我找到了一个例子,展示了一个模拟的例子,展示了什么是访问者模式的好处。这里有不同的Pill容器类:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

正如你在上面看到的,你BilsterPack包含对药片,所以你需要乘以2对的数量。此外,你可能会注意到瓶使用单位是不同的数据类型,需要强制转换。

所以在主要方法中,您可以使用以下代码计算药丸计数:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

注意,上面的代码违反了单一责任原则。这意味着如果添加新类型的容器,则必须更改主方法代码。同时,延长开关时间也是不好的做法。

通过引入以下代码:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

您将计数药丸数量的责任转移到名为PillCountVisitor的类(并且我们删除了switch case语句)。这意味着每当您需要添加新的药丸容器类型时,您应该只更改PillCountVisitor类。还要注意IVisitor接口一般用于其他场景。

通过在药丸容器类中添加Accept方法:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

我们允许访客参观药丸容器课程。

最后,我们计算药丸计数使用以下代码:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

这意味着:每个药片容器允许PillCountVisitor访问者查看他们的药片计数。他知道怎么数你的药。

看着来访者。伯爵有药丸的价值。

在 http://butunclebob.com/ArticleS.UncleBob.IuseVisitor你看到了真实的场景,你不能使用多态性(答案)来遵循单一责任原则。事实上在:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

reportQtdHoursAndPay方法用于报告和表示,这违反了单一责任原则。因此,最好利用访问者模式来解决这一问题。

我真的很喜欢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.