When should you really use the visitor pattern

后端 未结 5 1252
谎友^
谎友^ 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 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

         /// 
            /// Visitable class interface 
            /// 
           interface IMobileDevice
            {
               /// 
               /// It is the 'Accept' method of visitable class
               /// 
                /// Visitor Visiting the class
               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 -

    /// 
    /// Wiki Page - The Visitor pattern encodes a logical operation on the whole hierarchy into a single class containing one method per type. 
    /// 
    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)
    

提交回复
热议问题