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

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


当前回答

Quick description of the visitor pattern. The classes that require modification must all implement the 'accept' method. Clients call this accept method to perform some new action on that family of classes thereby extending their functionality. Clients are able to use this one accept method to perform a wide range of new actions by passing in a different visitor class for each specific action. A visitor class contains multiple overridden visit methods defining how to achieve that same specific action for every class within the family. These visit methods get passed an instance on which to work.

当你考虑使用它的时候

When you have a family of classes you know your going to have to add many new actions them all, but for some reason you are not able to alter or recompile the family of classes in the future. When you want to add a new action and have that new action entirely defined within one the visitor class rather than spread out across multiple classes. When your boss says you must produce a range of classes which must do something right now!... but nobody actually knows exactly what that something is yet.

其他回答

基于@Federico A. Ramponi的精彩回答。

想象一下你有这样的层次结构:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

如果你需要在这里添加一个“Walk”方法会发生什么?这对整个设计来说是痛苦的。

同时,添加“Walk”方法会生成新的问题。那"吃"和"睡"呢?我们真的必须为我们想要添加的每个新动作或操作添加一个新方法到Animal层次结构中吗?这很难看,但最重要的是,我们永远无法关闭Animal界面。因此,使用访问者模式,我们可以在不修改层次结构的情况下向层次结构添加新方法!

因此,只需检查并运行这个c#示例:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}

你的问题是什么时候知道:

我不首先编码访问者模式。我编写标准代码,等待需求的出现,然后重构。假设你有多个支付系统,一次安装一个。在签出时,你可以有很多if条件(或instanceOf),例如:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

现在假设我有10种支付方式,这有点难看。因此,当你看到这种模式发生时,访问者会很容易地将所有这些分离出来,然后你最终会调用这样的东西:

new PaymentCheckoutVistor(paymentType).visit()

你可以看到如何实现它从这里的例子的数量,我只是向你展示一个用例。

正如Konrad Rudolph已经指出的,它适用于需要双重调度的情况

这里有一个例子,以显示我们需要双重调度&访客如何帮助我们这样做的情况。

例子:

假设我有三种类型的移动设备——iPhone, Android, Windows mobile。

这三种设备都安装了蓝牙收音机。

让我们假设蓝牙收音机可以来自2个独立的原始设备制造商-英特尔和博通。

为了使这个例子与我们的讨论相关,我们还假设Intel电台公开的api与Broadcom电台公开的api是不同的。

这是我的类的样子

现在,我想介绍一个操作——移动设备蓝牙开关。

它的函数特征应该是这样的

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

因此,根据正确的设备类型和蓝牙收音机的正确类型,它可以通过调用适当的步骤或算法打开。

原则上,它变成了一个3 × 2的矩阵,在这里,我试图根据所涉及的对象的正确类型来进行正确的操作。

取决于两个参数类型的多态行为。

现在,访问者模式可以应用于这个问题。灵感来自维基百科页面上的说明——“本质上,访问者允许在不修改类本身的情况下向类族添加新的虚函数;相反,创建一个实现虚函数的所有适当专门化的访问者类。访问者将实例引用作为输入,通过双重调度实现目标。

由于3x2矩阵,双重调度是必要的

这是如何设置看起来像-

我写了一个例子来回答另一个问题,代码和它的解释在这里被提到。

你困惑的原因可能是来客是一个致命的用词不当。许多(杰出的)程序员都曾遇到过这个问题。它实际做的是用本身不支持它的语言(大多数语言不支持)实现双重调度。


1)我最喜欢的例子是《Effective c++》的作者Scott Meyers,他称这是他最重要的c++啊哈之一!的时刻。

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