Search code examples
c#design-patternsvisitor-pattern

Visiting pairs of visitables with the Visitor pattern


Is there a clean pattern that allows a visitor to visit two visitables at a time?

For example, if my visitor is a binary-addition operator, where it needs to know the data-type of two visitable inputs.

I have included a solution below, but feel it's messy because it requires that all the visitable objects include overloads for all concrete visitables, and also requires the visitor to include dummy visits to resolve the r-visitable.

If the visitor pattern isn't right for this, is there another pattern that is better suited?

Thank you

using System;
using System.Diagnostics;

namespace VisitorTest
{

   class Program
   {

      static void Main(string[] args)
      {

         // Given two visitable objects...
         IVisitable stringVisitable = new StringVisitable("987");
         IVisitable numberVisitable = new NumberVisitable(123);

         // And a visitor that performs a "lVisitable + rVisitable" operation...
         PlusOpVisitor plusOpVisitor = new PlusOpVisitor();

         // Test "string + string" == "987987"
         Console.WriteLine
         (  stringVisitable.PairAccept
            (  plusOpVisitor
            ,  stringVisitable
            )
         );

         // Test "string + number" == (convert both to string) == "987123"
         Console.WriteLine
         (  stringVisitable.PairAccept
            (  plusOpVisitor
            ,  numberVisitable
            )
         );

         // Test "number + string" == (convert both to number) == #1110
         Console.WriteLine
         (  numberVisitable.PairAccept
            (  plusOpVisitor
            ,  stringVisitable
            )
         );

         // Test "number + number" == #246
         Console.WriteLine
         (  numberVisitable.PairAccept
            (  plusOpVisitor
            ,  numberVisitable
            )
         );

      }

   }

   interface IPairVisitor
   {  // Messy: Dummies, just to know the l-visitable type, while visiting the r-visitable
      IVisitable Visit(StringVisitable lVisitable, IVisitable rVisitable);
      IVisitable Visit(NumberVisitable lVisitable, IVisitable rVisitable);
      // Actual visitor operations, what to do in each case
      IVisitable Visit(StringVisitable lVisitable, StringVisitable rVisitable);
      IVisitable Visit(NumberVisitable lVisitable, StringVisitable rVisitable);
      IVisitable Visit(StringVisitable lVisitable, NumberVisitable rVisitable);
      IVisitable Visit(NumberVisitable lVisitable, NumberVisitable rVisitable);
   }

   interface IVisitable
   {  // Resolve the l-visitable, include the unresolved r-visitable
      IVisitable PairAccept(IPairVisitor visitor, IVisitable  rVisitable);
      // Messy: Resolve the r-visitable, include the previously resolve l-visitable
      // The visitable-object must know the type of all objects that it can be
      // accepted against (normal visitor pattern doesn't have this restriction)
      IVisitable PairAccept(IPairVisitor visitor, StringVisitable lVisitable);
      IVisitable PairAccept(IPairVisitor visitor, NumberVisitable lVisitable);
   }

   class PlusOpVisitor : IPairVisitor
   {  // Repeat the accept, but for the other visitable
      public IVisitable Visit
      (  StringVisitable lVisitable
      ,  IVisitable      rVisitable
      ){ return rVisitable.PairAccept(this, lVisitable);
      }
      public IVisitable Visit
      (  NumberVisitable lVisitable
      ,  IVisitable      rVisitable
      ){ return rVisitable.PairAccept(this, lVisitable);
      }
      // Perform the actual operation for each pair
      public IVisitable Visit
      (  StringVisitable lVisitable
      ,  StringVisitable rVisitable
      ){ return new StringVisitable
         (  string.Concat
            (  lVisitable.Value
            ,  rVisitable.Value
            )
         );
      }
      public IVisitable Visit
      (  StringVisitable lVisitable
      ,  NumberVisitable rVisitable
      ){ return new StringVisitable
         (  string.Concat
            (  lVisitable.Value
            ,  rVisitable.Value.ToString()
            )
         );
      }
      public IVisitable Visit
      (  NumberVisitable lVisitable
      ,  StringVisitable rVisitable
      ){ return new NumberVisitable
         (  lVisitable.Value
          + int.Parse(rVisitable.Value)
         );
      }
      public IVisitable Visit
      (  NumberVisitable lVisitable
      ,  NumberVisitable rVisitable
      ){ return new NumberVisitable
         (  lVisitable.Value
          + rVisitable.Value
         );
      }
   }

   class StringVisitable : IVisitable
   {  public StringVisitable
      (  string value
      ){ _value = value;
      }
      public string Value
      {  get { return _value; }
      }
      // Visit as an l-visitable, r-visitable still unresolved
      public IVisitable PairAccept
      (  IPairVisitor  visitor
      ,  IVisitable rVisitable
      ){ return visitor.Visit(this, rVisitable);
      }
      // Visit as an r-visitable, l-visitable resolved
      public IVisitable PairAccept
      (  IPairVisitor       visitor
      ,  StringVisitable lVisitable
      ){ return visitor.Visit(lVisitable, this);
      }
      public IVisitable PairAccept
      (  IPairVisitor       visitor
      ,  NumberVisitable lVisitable
      ){ return visitor.Visit(lVisitable, this);
      }
      public override string ToString()
      {  return string.Concat("\"", _value, "\"");
      }
      private string _value;
   }

   class NumberVisitable : IVisitable
   {  public NumberVisitable
      (  int value
      ){ _value = value;
      }
      public int Value
      {  get { return _value; }
      }
      public IVisitable PairAccept
      (  IPairVisitor  visitor
      ,  IVisitable rVisitable
      ){ return visitor.Visit(this, rVisitable);
      }
      public IVisitable PairAccept
      (  IPairVisitor       visitor
      ,  StringVisitable lVisitable
      ){ return visitor.Visit(lVisitable, this);
      }
      public IVisitable PairAccept
      (  IPairVisitor       visitor
      ,  NumberVisitable lVisitable
      ){ return visitor.Visit(lVisitable, this);
      }
      public override string ToString()
      {  return string.Concat("#", _value);
      }
      private int _value;
   }

}

Solution

  • It looks like you are actually looking for the interpreter pattern. You might also want to check out expression trees.