Reputation: 342
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
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
Reputation: 1269
It looks like you are actually looking for the interpreter pattern. You might also want to check out expression trees.
Upvotes: 0