When should you really use the visitor pattern

后端 未结 5 1257
谎友^
谎友^ 2021-02-06 10:29

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

相关标签:
5条回答
  • 2021-02-06 10:53

    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 refer to this also: https://www.oodesign.com/visitor-pattern.html

    0 讨论(0)
  • 2021-02-06 10:57

    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.

    0 讨论(0)
  • 2021-02-06 11:00

    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.

    0 讨论(0)
  • 2021-02-06 11:07

    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.
    0 讨论(0)
  • 2021-02-06 11:11

    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)
    
    0 讨论(0)
提交回复
热议问题