Eliott
Eliott

Reputation: 342

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

}

Upvotes: 1

Views: 140

Answers (2)

Eliott
Eliott

Reputation: 342

This can be achieved using the typical visitor pattern, except with triple-dispatch, where a visitor is created to resolve the LHS in isolation while storing the unresolved RHS, then another visitor is created to resolve the RHS while storing the resolved LHS.

using System;
using System.Diagnostics;

namespace VisitorTest
{

   class Program
   {

      static void Main(string[] args)
      {

         // Given two Acceptable objects...
         IAcceptor stringAcceptor = new StringAcceptor("987");
         IAcceptor numberAcceptor = new NumberAcceptor(123);

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

         // Test "string + string" == "987987"
         Console.WriteLine
         (  plusOpVisitor.Visit
            (  stringAcceptor
            ,  stringAcceptor
            )
         );

         // Test "string + number" == (convert both to string) == "987123"
         Console.WriteLine
         (  plusOpVisitor.Visit
            (  stringAcceptor
            ,  numberAcceptor
            )
         );

         // Test "number + string" == (convert both to number) == #1110
         Console.WriteLine
         (  plusOpVisitor.Visit
            (  numberAcceptor
            ,  stringAcceptor
            )
         );

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

      }

   }

   // Typical visitor/acceptor, no concrete types exposed
   interface IVisitor
   {  IAcceptor Visit(StringAcceptor acceptor);
      IAcceptor Visit(NumberAcceptor acceptor);
   }

   interface IAcceptor
   {  IAcceptor Accept(IVisitor visitor);
   }

   class PlusOpVisitor
   {

      public IAcceptor Visit(IAcceptor lAcceptor, IAcceptor rAcceptor)
      {  return lAcceptor.Accept(new LHSVisitor(rAcceptor));
      }

      // Visitor to handle the left-hand-side, holding the right-hand-side
      //  until the LHS is resolvd
      private class LHSVisitor : IVisitor
      {

         public LHSVisitor(IAcceptor rAcceptor)
         {  _rAcceptor = rAcceptor;
         }

         // With the LHS resolved, create a new visitor to handle the RHS
         //  storing the resolve LHS
         public IAcceptor Visit(StringAcceptor lAcceptor)
         {  return _rAcceptor.Accept(new LHSStringRHSVisitor(lAcceptor));
         }

         public IAcceptor Visit(NumberAcceptor lAcceptor)
         {  return _rAcceptor.Accept(new LHSNumberRHSVisitor(lAcceptor));
         }

         private readonly IAcceptor _rAcceptor;


      }

      // Visitor to handle the RHS when the LHS has been resolved as a string
      private class LHSStringRHSVisitor : IVisitor
      {

         public LHSStringRHSVisitor(StringAcceptor lAcceptor)
         {  _lAcceptor = lAcceptor;
         }

         // Now both LHS and RHS are resolved
         public IAcceptor Visit(StringAcceptor rAcceptor)
         {  return new StringAcceptor
            (  string.Concat(_lAcceptor.Value, rAcceptor.Value)
            );
         }

         public IAcceptor Visit(NumberAcceptor rAcceptor)
         {  return new StringAcceptor
            (  string.Concat(_lAcceptor.Value, rAcceptor.Value.ToString())
            );
         }

         private readonly StringAcceptor _lAcceptor;

      }

      // Visitor to handle the RHS when the LHS has been resolved as a number
      private class LHSNumberRHSVisitor : IVisitor
      {

         public LHSNumberRHSVisitor(NumberAcceptor lAcceptor)
         {  _lAcceptor = lAcceptor;
         }

         public IAcceptor Visit(StringAcceptor rAcceptor)
         {  return new NumberAcceptor
            (  _lAcceptor.Value + int.Parse(rAcceptor.Value)
            );
         }

         public IAcceptor Visit(NumberAcceptor rAcceptor)
         {  return new NumberAcceptor(_lAcceptor.Value + rAcceptor.Value);
         }

         private readonly NumberAcceptor _lAcceptor;

      }

   }

   class StringAcceptor : IAcceptor
   {  public StringAcceptor
      (  string value
      ){ _value = value;
      }
      public string Value
      {  get { return _value; }
      }
      public IAcceptor Accept(IVisitor visitor)
      {  return visitor.Visit(this);
      }
      public override string ToString()
      {  return string.Concat("\"", _value, "\"");
      }
      private string _value;
   }

   class NumberAcceptor : IAcceptor
   {  public NumberAcceptor
      (  int value
      ){ _value = value;
      }
      public int Value
      {  get { return _value; }
      }
      public IAcceptor Accept(IVisitor visitor)
      {  return visitor.Visit(this);
      }
      public override string ToString()
      {  return string.Concat("#", _value);
      }
      private int _value;
   }

}

Upvotes: 0

Yuli Bonner
Yuli Bonner

Reputation: 1269

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

Upvotes: 0

Related Questions