什么时候应该真正使用访问者模式 [英] When should you really use the visitor pattern

查看:189
本文介绍了什么时候应该真正使用访问者模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在将此标记为重复之前,请允许我澄清自己。
我正在阅读访问者模式及其适用的用途。



我偶然发现了这个帖子:



现在,我想介绍一个操作 - 在移动设备上切换蓝牙。



它的函数签名应该像这样 -

  void SwitchOnBlueTooth (IMobileDevice mobileDevice,IBlueToothRadio blueToothRadio)

因此取决于设备类型根据蓝牙无线电的正确类型,可以通过调用相应的步骤或算法打开。



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



根据两个参数类型的多态行为。





现在,我将介绍Visitor模式来解决这个问题。灵感来自维基百科页面,说明 - 实质上,访问者允许一个添加新的虚拟函数到类的家庭,而无需修改类本身;而是创建一个访问者类,实现虚拟函数的所有适当的专门化。访问者将实例引用作为输入,并通过双调度实现目标。



矩阵



代码中的访问者模式介绍 -



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



以下是设置





这里是客户端代码&测试代码

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

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

这里是类的层次结构

  ///< summary> 
///可见类接口
///< / summary>
interface IMobileDevice
{
///< summary>
///它是可访问类的'Accept'方法
///< / summary>
///< param name =blueToothVisitor>访问者访问类< / 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 (这个);
}
}

class WindowsMo​​bile:IMobileDevice
{
public void TurnOn(IBlueToothVisitor blueToothVisitor)
{
blueToothVisitor.SwitchOn (这个);
}
}

interface IBlueToothRadio
{

}

类BroadComBlueToothRadio:IBlueToothRadio
{

}

class IntelBlueToothRadio:IBlueToothRadio
{

}

访问者遵循 -

  ///< summary> 
/// Wiki页面 - 访问者模式将整个层次结构上的逻辑操作编码为单个类,每个类型包含一个方法。
///< / summary>
interface IBlueToothVisitor
{
void SwitchOn(iPhone device);
void SwitchOn(WindowsMo​​bile device);
void SwitchOn(Android设备);
}


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

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

public void SwitchOn(WindowsMo​​bile设备)
{
Console.WriteLine(在Windows Mobile上在英特尔无线电上切换);
}

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

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

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

public void SwitchOn(WindowsMo​​bile device)
{
Console.WriteLine(在Windows Mobile上的BroadCom广播上切换);
}

public void SwitchOn(Android device)
{
Console.WriteLine(Swithing On BroadCom radio on Android);让我来看看这个结构的一些点 - / p>


  1. 我有2位蓝牙访问者,其中包含在每种类型的移动设备上打开蓝牙的算法

  2. <我已经保持BluetoothVistor& BluetoothRadio独立的,以坚持访问者的理念 - 添加操作而不修改类本身。
  3. 每个访问者都定义了3个功能 - 每个类型的移动设备一个。

  4. 此处由于存在6种算法变体(取决于对象类型)双调度是必要的。

  5. 正如我上面写的我的原始要求是有一个像这样的函数 - void SwitchOnBlueTooth(IMobileDevice mobileDevice,IBlueToothRadio blueToothRadio),现在为双调度工作我已经更改签名 - 而不是 IBlueToothRadio 我使用 IBlueToothVisitor

请让我知道,如果任何位不清楚,我们可以进一步讨论。



PS:我回答了一个问题,有点在同一行,但它有一个不同的范围&






$ 1

根据注释,我删除了包装器 IBlueToothVisitor



这是访问者模式看起来像没有这个包装器 -



但它仍然是访问者模式


  1. IMobileDevice 是Visitable类别的介面。


  2. IMobileDevice.TurnOn 是可访问类的Accept方法。但现在它接受 IBlueToothRadio 作为访问者而不是 IBlueToothVisitor


  3. IBlueToothRadio 成为新的访问类接口。


因此,客户端中的函数签名现在改为使用
IBlueToothRadio

  public void SwitchOnBlueTooth(IMobileDevice mobileDevice,IBlueToothRadio blueToothRadio)


Ok before marking this as a duplicate let me clarify myself. I'm reading about the visitor pattern and its applicable uses.

I've stumbled upon this post: When should I use the Visitor Design Pattern?

and the user who wrote the first answer says as follow :

Now we want to add a new operation to the hierarchy, namely we want each animal to make its sound. As far as the hierarchy is this simple, you can do it with straight polymorphism:
...
But proceeding in this way, each time you want to add an operation you must modify the interface to every single class of the hierarchy.

Now, I mostly see why it's needed from his perspective, it's basically a way to cut compilation time by making it so not every time you want to add a new polymorphic method to a class hierarchy, the whole hierarchy will get recompiled.

But he's also saying that it's fine adding a new polymorphic method to the hierarchy as long as it's a "simple" hierarchy. But my question is when do you set your line and decide on what is simple and what is not.
Also, what if an hierarchy is a complex one, but adding a new method just make total sense to be and instance method rather than having the operation in a complete different class ?

Searching a bit more I found this article explaining the visitor pattern and it's uses http://butunclebob.com/ArticleS.UncleBob.IuseVisitor

The author gave an example where writing an instance method makes the object coupled to something and moving the method to a different class (The visitor) breaks the decoupling. This made more sense to me, but I'm still not quite sure when this pattern should really be used, the first argument of "changing the hierarchy time every time you want to add a new polymorphic method..." seems to me like an excuse because if a method seems to fit in the hierarchy it should be there logically, Assuming the Animal example was a really complex hierarchy and I would have decided to add make sound method, adding an instance method would be a logical choice (in my mind).
But maybe I'm wrong so I'm here asking for more insight about this, maybe someone could enlighten me.

解决方案

Visitor pattern is suitable for incases where we need double dispatch, and the programming language does not support it.

I'm a C# developer & C# does not support it directly, And I think neither does C++. (although in newer version of C#, post C# 4.0, there is dynamic key word which may do the trick).

I'll take an example to show a situation where we need double dispatch & how visitor helps us in doing so. (Since I'm a C# developer, my code base is in C#, please bear with me, but I promise, I have tried to keep it as language neutral as possible)

Example :

Lets say I have 3 types of mobile devices - iPhone, Android, Windows Mobile.

All these three devices have a Bluetooth radio installed in them.

Lets assume that the blue tooth radio can be from 2 separate OEMs – Intel & Broadcom.

Just to make the example relevant for our discussion, lets also assume that the APIs exposes by Intel radio are different from the ones exposed by Broadcom radio.

This is how my classes look –

Now, I would like to introduce an operation – Switching On the Bluetooth on mobile device.

Its function signature should like something like this –

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

So depending upon Right type of device and Depending upon right type of Bluetooth radio, it can be switched on by calling appropriate steps or algorithm.

In principal, it becomes a 3 x 2 matrix, where-in I’m trying to vector the right operation depending upon the right type of objects involved.

A polymorphic behaviour depending upon the type of both the arguments.

Now, I’ll introduce Visitor pattern to this problem. Inspiration comes from the Wikipedia page stating – "In essence, the visitor allows one to add new virtual functions to a family of classes without modifying the classes themselves; instead, one creates a visitor class that implements all of the appropriate specializations of the virtual function. The visitor takes the instance reference as input, and implements the goal through double dispatch."

Double dispatch is a necessity here due to the 3x2 matrix

Introducing Visitor pattern in code -

I have to make a decision first, which class hierarchy is more stable (less susceptible to change) – Device class hierarchy or the blue tooth class hierarchy. The one more stable will become the visitable classes & the less stable one will become visitor class. For this example, I’ll say the device class is more stable.

Here is the set-up

Here is client code & test code

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

Here are the hierarchy of classes

     /// <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
        {

        }

The visitors follow -

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

Let me walk through some points of this structure –

  1. I have 2 Bluetooth visitors, which contain the algorithm to switch on Bluetooth on each type of mobile device
  2. I have kept BluetoothVistor & BluetoothRadio separate so as to stick to the visitor philosophy – "Add operations without modifying the classes themselves ". May be others would like to merge it into BluetoothRadio class itself.
  3. Each visitor has 3 functions defined – one for each type mobile device.
  4. Also here since 6 variants of algorithm exist (depending upon the type of object) double dispatch is a necessity.
  5. As I wrote above my original requirement was to have a function like this - void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio), now for double dispatch to work I have changed the signature – instead of IBlueToothRadio I use IBlueToothVisitor

Please let me know if any bit is unclear we can discuss it further.

PS: I answered a question, somewhat on the same lines but it had a different scope & references, therefor I have taken out relevant parts from my previous answer here for clarity.


Edit 1

As per the comments, I'm removing the wrapper IBlueToothVisitor

This is how the visitor pattern will look like without this wrapper -

However its still is a visitor pattern

  1. IMobileDevice is the Visitable class interface.

  2. IMobileDevice.TurnOn is the 'Accept' method of visitable class. But now it accepts IBlueToothRadio as the visitor instead of IBlueToothVisitor

  3. IBlueToothRadio becomes the new visitor class interface.

Thereby the The function signature in client is now changed to use IBlueToothRadio

  public void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

这篇关于什么时候应该真正使用访问者模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆