When should you really use the visitor pattern
Asked Answered
I

5

11

In the post When should I use the Visitor Design Pattern? the first answerer states :

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 a hierarchy is a complex one, but adding a new method just makes total sense to be and instance method rather than having the operation in a complete different class ?

In this article the author gives 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.

Idzik answered 31/10, 2015 at 21:43 Comment(1)
Animal examples in OO are really bad sometimes. Head First Design Patterns uses a hierarchy of meals that can be ordered in a diner. Each meal can be composed of ingredients, etc. Now, imagine that customers want to know information such as getHealthRating() or getCarbs() or some new fangled information that will change next year when the government comes out with new standards for our health. It would be painful to implement all these methods/functions in the hierarchy. So, we use a visitor. The key (to me) is to understand that visitor code can (and is expected to) easily change.Janeejaneen
L
13

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 –

enter image description here enter image description here

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.

enter image description here

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

enter image description here

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 - enter image description here

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)
Lesbian answered 1/11, 2015 at 9:7 Comment(10)
Sorry for the late response, was away from home. Thanks for the detailed explanation, but something still bothers me in your example as to why using visitor is necessary. Removing all the Visitor related classes from the hierarchy, you could add those 3 SwitchOn() Methods (1 for each mobile device) to the interface IBlueToothRadio, and have each IBlueToothRadio implementer, implement them, and have the devices TurnOn() methods get an IBlueToothRadio which calls blueToothRadio.SwitchOn(this), this will achieve double dispatch if I'm not mistaken, so why the visitor is necessary?Idzik
Hello @JoJoDmlx, Yes you are absolutely correct, I can define the 3 SwitchOn() Methods (1 for each mobile device) in the interface IBlueToothRadio, and this will give me double dispatch. And in such a case also, we'll be sticking to visitor patterm - In this case the IBlueToothRadio hierarchy will become the visitor class, IMobileDevice hierarchy stays as visitable class - as it is now. IMobileDevice.TurnOn remains as the 'Accept' method of visitable classLesbian
In my example I wrote an extra wrapper over it, As I also mentioned in my answer 2nd point - 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.Lesbian
Missed the last part of the 2nd bullet point. So basically shoving the SwitchOn() methods to the IBlueToothRadio interface instead of using the visitor wrappers is still considered implementing the visitor pattern ? And as to why use the wrapper, the only reason I see is to let other developers who don't have access to the code (maybe you're writing a library) to extend your code, is that correct ? Are there any more reasons ?Idzik
@JoJoDmlx: I just made an edit to my answer to include the setup you suggested. To answer your question as to why I used a wrapper ... In the new setup you see that every time I add a new device, I'll also make a change to BluetoothRadio interface, in my example I wanted to prevent that, so I had introduced a wrapper. Please let me know if there is any bit I can explain or if you think I should add the code for the new setup,Lesbian
Why would you want to prevent doing changes to your IBlueToothRadio interface ? Does it benefits for compilation time ? Or is it something else. Assuming you have access the code at all times why wouldn't you want to change the interface ?Idzik
Compile time benefit, yes it will be when I interface sits in another library and there are a lot of libraries dependent on this. However, this was not the reason I introduced the wrapper. In my example I took Devices & Bluetooth Radio, but I dont expect that for every device the radio will change, perhaps the way the radio is configured for the particular device will change therefore I introduced a wrapper, the wrapper represented a configrator.Lesbian
Certainly the visitor will always change for a new visitable class. therefore When implementing a visitor pattern 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 chose the device class to be more stable.Lesbian
One more point, since we are on this note, as you can observe that there is a ripple factor here, every time a new visitable class is added, all visitor classes must be changed to implement the new interface. Wheather they are effected by new visitable class or not. However there is a variant of visitor pattern - Acyclic visitor, which works well in such cases. The one I tried to explain is the basic visitor pattern, just for double dispatch. About Acyclic variant in C++ , I just google this fig.net/resources/proceedings/fig_proceedings/athens/papers/…Lesbian
@Lesbian I know that this is an old answer, but is it really necessary to have the turnOn method in the IMobileInterface? You can just apply the switchOn method upon the needed device.Indo
P
5

I think the important thing is not how complex your hierarchy is, it is how fixed the hierarchy is.

If your hierarchy is unlikely to change but the operations you want to perform on the classes is likely to change then you may have a candidate for the visitor pattern.

It is all a compromise, so it is hard to draw a "line". You just need to decide which design will be more manageable in the long run given your requirements.

For example, you may not want your Animal class to have lots of member functions like printToPDF(), getMigrationReport5(), etc and a visitor pattern would have been better.

Or perhaps, you want to be able to easily add a Tortoise to your hierarchy of Animals without breaking any existing code. In which case the visitor pattern may not be such a good idea.

There is a third option and that is to use some sort of pattern matching. But it is currently hard to do that elegantly in C++ without some external library.

Poise answered 31/10, 2015 at 22:26 Comment(3)
And on what situation will you consider your hierarchy fixed ? Unless you don't have the source code for it which I see why but then again if you don't have the source code for it there's no guarantee that it implements a visitor pattern. Also seeing the type of methods you mention, what will be the difference between making a visitor pattern say AnimalPdfPrinter that visit every kind of animal or just making a static class/free functions that does the same ?Idzik
@JoJoDmix The visitor pattern enables double dispatch so the AnimalPdfPrinter can treat different types of Animals differently. A static class/free function can't do that (without some kind of pattern matching).Poise
@JoJoDmix if you know all the classes in your hierarchy up front and are unlikely to need any more then I would call that "fixed". You may also have a hierarchy in a third party library that accepts a visitor. You typically have access to header files so you can see is it accepts a visitor.Poise
S
3

The Visitor pattern is for composing things. Basically that's when you use the visitor pattern: you have a non trivial operation which needs to compose primitive operation/properties and may need to adapt its behaviour depending on the type of value it works with. You'd put the primitive operations and properties in the class hierarchy itself, and extract the composition of those things into a visitor pattern type operation.

Essentially the visitor pattern is a crutch for languages which lack 'true' polymorphism[1] and higher order functions. Otherwise you'd simply define the composition of operations as a higher order polymorphic function foo() that simply takes helper functions as parameters to resolve the specifics.

So it's a limitation of the language more than a strength of design. If you use this sort of thing a lot to cut down on compilation times, you should consider whether you are trying to work against the tool or with the tool, i.e. whether you are doing the equivalent of a "real programmer who can program Fortran in any language". (And whether it is perhaps time to pick up/learn a different tool, besides the hammer.)

  1. By which I mean the ability of functions to work on values of "arbitrary types" without having to nail down the type to something specific (A or B), meaning the exact type/signature of the function/call changes depending on what you pass in.
Serenata answered 31/10, 2015 at 22:15 Comment(1)
Perhaps the term multimethod would make part of this description clearer?Almanac
K
1

When I've seen the Visitor pattern recommended on SO, it is nearly always in order to avoid type checking. The Visitor pattern is therefore applied to a small class hierarchy, particularly when every subclass can be listed out in code.

Questions that lead to this pattern often start with, "I have a long chain of if-else statements where each condition tests for a different class from a hierarchy. How can I avoid so many if-else statements?"

For example, say I have a scheduling application with a class called Day which has seven subclasses (one for each day of the week). The naïve approach would be to implement this scheduler using if-else or switch-case.

// pseudocode
if (day == Monday)
    // Monday logic
else if (day == Tuesday)
    // Tuesday logic
// etc.

The Visitor pattern avoids this chain of conditional logic by taking advantage of double dispatch. The seven days become seven methods, with the appropriate method chosen at runtime based on the type of Day argument which is passed.

Note that neither the Visitor pattern nor any other GoF pattern is used to decrease compilation time. Modifying any pre-existing interface can be difficult and error-prone. The Visitor pattern allows new type-based functionality to be added without modifying existing classes.

Kishakishinev answered 31/10, 2015 at 23:29 Comment(3)
isn't it dynamic dispatch in your example ? A pure virtual method which each subclass give it's own implementation. Also if I choose to add new functionality to classes of the same hierarchy without modifying a common interface won't it help reduce compilation time because instead of doing changes to N number of compilation units, where N is the number of classes in the hierarchy, I do changes just in one class, the new Visitor introduced ?Idzik
The Visitor design pattern utilizes double dispatch.Kishakishinev
The purpose of design patterns is to standardize solutions to common coding design problems, rather than to optimize compilation. While some patterns may happen to reduce compilation time, that is a happy accident rather than an intended feature. One would not choose a design pattern for the purpose of optimizing compilation.Kishakishinev
A
0

You can use the visitor pattern to design a db saver, like shown in this diagram here: class diagram for an object saver in different dbs

you can choose what db to use, the client class will look similar to this:

    Visitor mongosaver = new MongodbSaver();
    Visitable user = new User();
    //save the user object using mongodb saver
    user.accept(mongosaver);
    Visitable post = new BlogPost();
    Visitor mysqlsaver = new MysqlSaver();
    //save the BlogPost using Mysql saver
    post.accept(mysqlsaver);

You can also refer to this.

Alpers answered 30/5, 2020 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.