什么时候应该真正使用访客模式

JoJ*_*mlx 5 c++ design-patterns visitor

好吧,在将此标记为重复之前让我自己澄清一下.我正在阅读访客模式及其适用用途.

我偶然发现了这篇文章: 我什么时候应该使用访客设计模式?

编写第一个答案的用户说如下:

现在我们想要在层次结构中添加一个新操作,即我们希望每个动物发出声音.对于层次结构这么简单,您可以使用直接多态:
...
但是以这种方式进行,每次要添加操作时,必须将接口修改为层次结构的每个类.

现在,我基本上从他的角度看出为什么需要它,它基本上是一种减少编译时间的方法,因为不是每次你想要将新的多态方法添加到类层次结构时,整个层次结构都会被重新编译.

但他也说,只要它是一个"简单"的层次结构,就可以在层次结构中添加一个新的多态方法.但我的问题是你什么时候设定你的界限并决定什么是简单的,什么不是.
此外,如果层次结构是一个复杂的层次结构,但添加一个新方法只是使得和实例方法完全合理,而不是在一个完全不同的类中进行操作?

再搜索一下,我发现这篇文章解释了访客模式,并使用了 http://butunclebob.com/ArticleS.UncleBob.IuseVisitor

作者给出了一个示例,其中编写实例方法使对象耦合到某个东西并将该方法移动到另一个类(访问者)中断了解耦.这对我来说更有意义,但我仍然不太确定何时应该真正使用这个模式,"每次你想要添加一个新的多态方法时改变层次结构时间的第一个论点......"在我看来似乎一个借口,因为如果一个方法似乎适合层次结构它应该在逻辑上,假设动物示例是一个非常复杂的层次结构,我会决定添加make sound方法,添加一个实例方法将是一个合乎逻辑的选择(在我的心神).
但也许我错了,所以我在这里要求更多地了解这一点,也许有人可以启发我.

Kap*_*oor 6

访问者模式适用于需要双重调度的情况,编程语言不支持它.

我是C#开发人员而C#不直接支持它,我认为C++也不支持.(虽然在C#的较新版本中,在C#4.0之后,有一个dynamic关键词可以做到这一点).

我将举例说明我们需要双重调度的情况以及访问者如何帮助我们这样做.(因为我是C#开发人员,我的代码库是C#,请耐心等待,但我保证,我尽量保持语言中性)

示例:

可以说我有3种类型的移动设备 - iPhone,Android,Windows Mobile.

所有这三个设备都安装了蓝牙无线电.

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

为了使示例与我们的讨论相关,我们还假设英特尔无线电公开的API与Broadcom无线电公开的API不同.

这就是我的课程外观 -

在此输入图像描述 在此输入图像描述

现在,我想介绍一项操作 - 在移动设备上打开蓝牙.

它的功能签名应该像这样 -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)
Run Code Online (Sandbox Code Playgroud)

所以,这取决于权类型的设备,并根据不同类型正确蓝牙无线电的,它可以通过接通调用适当的步骤或算法.

原则上,它变成一个3 x 2矩阵,其中我试图根据所涉及的对象的正确类型来引导正确的操作.

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

在此输入图像描述

现在,我将介绍访问者模式到这个问题.灵感来自维基百科页面,指出 - "实质上,访问者允许一个人在不修改类本身的情况下向一个类族添加新的虚函数; 相反,我们创建了一个访问者类,它实现了虚函数的所有适当的特化.访问者将实例引用作为输入,并通过双重调度实现目标."

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

在代码中引入访客模式 -

我必须首先做出决定,哪个类层次结构更稳定(更不容易改变) - 设备类层次结构或蓝牙类层次结构.一个更稳定的将成为可访问的类和不太稳定的将成为访客类.对于这个例子,我会说设备类更稳定.

这是设置

在此输入图像描述

这是客户端代码和测试代码

 class Client
  {
      public void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothVisitor blueToothRadio) 
      {
          mobileDevice.TurnOn(blueToothRadio);        
      }
  }


 [TestClass]
public class VisitorPattern
{

    Client mClient = new Client();

    [TestMethod]
    public void AndroidOverBroadCom()
    {
        IMobileDevice device = new Android();
        IBlueToothVisitor btVisitor = new BroadComBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void AndroidOverIntel()
    {
        IMobileDevice device = new Android();
        IBlueToothVisitor btVisitor = new IntelBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void iPhoneOverBroadCom()
    {
        IMobileDevice device = new iPhone();
        IBlueToothVisitor btVisitor = new BroadComBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void iPhoneOverIntel()
    {
        IMobileDevice device = new iPhone();
        IBlueToothVisitor btVisitor = new IntelBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是类的层次结构

     /// <summary>
        /// Visitable class interface 
        /// </summary>
       interface IMobileDevice
        {
           /// <summary>
           /// It is the 'Accept' method of visitable class
           /// </summary>
            /// <param name="blueToothVisitor">Visitor Visiting the class</param>
           void TurnOn(IBlueToothVisitor blueToothVisitor);
        }

       class iPhone : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

       class Android : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

       class WindowsMobile : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

        interface IBlueToothRadio
        {

        }

        class BroadComBlueToothRadio : IBlueToothRadio
        {

        }

        class IntelBlueToothRadio : IBlueToothRadio
        {

        }
Run Code Online (Sandbox Code Playgroud)

访客关注 -

/// <summary>
/// Wiki Page - The Visitor pattern encodes a logical operation on the whole hierarchy into a single class containing one method per type. 
/// </summary>
interface IBlueToothVisitor
{
    void SwitchOn(iPhone device);
    void SwitchOn(WindowsMobile device);
    void SwitchOn(Android device);
}


class IntelBlueToothVisitor : IBlueToothVisitor
{
    IBlueToothRadio intelRadio = new IntelBlueToothRadio();

    public void SwitchOn(iPhone device)
    {
        Console.WriteLine("Swithing On intel radio on iPhone");
    }

    public void SwitchOn(WindowsMobile device)
    {
        Console.WriteLine("Swithing On intel radio on Windows Mobile");
    }

    public void SwitchOn(Android device)
    {
        Console.WriteLine("Swithing On intel radio on Android");
    }
}

class BroadComBlueToothVisitor : IBlueToothVisitor
{
    IBlueToothRadio broadCom = new BroadComBlueToothRadio();

    public void SwitchOn(iPhone device)
    {
        Console.WriteLine("Swithing On BroadCom radio on iPhone");
    }

    public void SwitchOn(WindowsMobile device)
    {
        Console.WriteLine("Swithing On BroadCom radio on Windows Mobile");
    }

    public void SwitchOn(Android device)
    {
        Console.WriteLine("Swithing On BroadCom radio on Android");
    }
}
Run Code Online (Sandbox Code Playgroud)

让我来看看这个结构的一些要点 -

  1. 我有2个蓝牙访问者,其中包含在每种类型的移动设备上打开蓝牙的算法
  2. 我一直保持BluetoothVistor和BluetoothRadio的独立性,以便坚持访客的理念 - "在不修改类本身的情况下添加操作".可能是其他人想把它合并到BluetoothRadio类本身.
  3. 每个访问者定义了3个功能 - 每个类型的移动设备一个.
  4. 此外,由于存在6种算法变体(取决于对象的类型),因此需要双重调度.
  5. 正如我上面写的那样,我原来的要求就是拥有这样的功能 - void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)现在为了双重调度工作,我改变了签名 - 而不是IBlueToothRadio我使用IBlueToothVisitor

如果不清楚我们可以进一步讨论,请告诉我.

PS:我回答了一个问题,有点相同,但它有不同的范围和参考,因此为了清楚起见,我从我之前的答案中取出了相关部分.


编辑1

根据评论,我正在删除包装器 IBlueToothVisitor

这就是没有这个包装器的访问者模式的样子 - 在此输入图像描述

然而它仍然是访客模式

  1. IMobileDevice 是Visitable类接口.

  2. IMobileDevice.TurnOn是可访问类的"接受"方法.但现在它接受IBlueToothRadio了访客而不是IBlueToothVisitor

  3. IBlueToothRadio 成为新的访客类界面.

从而客户端中的函数签名现在更改为使用 IBlueToothRadio

  public void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)
Run Code Online (Sandbox Code Playgroud)


Chr*_*rew 5

我认为重要的不是你的层次结构有多复杂,而是 层次结构有多固定。

如果您的层次结构不太可能更改,但您想要在类上执行的操作可能会更改,那么您可能有访问者模式的候选者。

这都是一种妥协,因此很难划清“界限”。您只需根据您的要求决定哪种设计从长远来看更易于管理。

例如,您可能不希望您的Animal类有很多成员函数,如printToPDF()getMigrationReport5()等,而访问者模式会更好。

或者,您可能希望能够轻松地将 a 添加Tortoise到 s 的层次结构中Animal,而不破坏任何现有代码。在这种情况下,访问者模式可能不是一个好主意。

还有第三种选择,那就是使用某种模式匹配。但目前如果没有一些外部库,在 C++ 中很难优雅地做到这一点。


use*_*396 3

访问者模式用于组合事物。基本上,这就是您使用访问者模式的时候:您有一个不平凡的操作,它需要组成原始操作/属性,并且可能需要根据它所使用的值的类型来调整其行为。您可以将原始操作和属性放入类层次结构本身中,并将这些内容的组合提取到访问者模式类型操作中。

本质上,访问者模式是缺乏“真正的”多态性[1]和高阶函数的语言的拐杖。否则,您只需将操作的组合定义为高阶多态函数,foo()该函数仅将辅助函数作为参数来解决具体问题。

因此,这是语言的限制,而不是设计的优势。如果你经常使用这种东西来减少编译时间,你应该考虑你是在尝试对抗该工具还是使用该工具,即你是否正在做相当于“可以用 Fortran 编程的真正程序员”的工作。任何语言”。(以及除了锤子之外,是否是时候拿起/学习不同的工具了。)

  1. 我的意思是函数能够处理“任意类型”的值,而不必将类型确定为特定的东西(A或B),这意味着函数/调用的确切类型/签名会根据您传递的内容而变化在。