Reputation: 181
I am attempting to implement the Visitor design pattern. I have a basic understanding of the Visitor pattern and its classic implementation. I am attempting to morph the visitor pattern to meet my specific goal.
Specific Goal: Add a Return Type to the Visitor Pattern that is specific to which Visitable(node) is being visited.
The classic examples I've seen so far of a visitor returning a specific return type can be achived by generics. But this does not meet my goal: Add a Return Type to the Visitor Pattern that is specific to which Visitable(node) is being visited.
Here's the other SO questions I looked at before posting mine: Visitor Design Pattern - return type java - Return a value from Visitor
Implementing Visitor Pattern while allowing different return types of functions --> This question did offer a solution (ie store state inside the visitor, but I didn't love that the caller or consumer of the API would need to remember to get the state back out after calling visit.
Here's some code to show what I mean:
public interface IVisitor<TReturn>
{
TReturn Visit(VisitableClassReturnString visitableClass);
TReturn Visit(VisitibleClassReturnInt visitibleClass);
}
public class Visitor : IVisitor<string>
{
public string Visit(VisitableClassReturnString visitableClass)
{
// Return type is correct.
// Logic for visiting (just examples for a return typebelow)
return string.Empty;
}
public string Visit(VisitibleClassReturnInt visitibleClass)
{
// Return type is NOT correct.
// Logic for visiting (just examples for a return typebelow)
return 0;
}
}
As shown in the code above, I have two different Visitable Classes (nodes) that my visitor wants to visit, in my specific application when I visit those two nodes, I need to return a DIFFERENT type upon visit. Also important to note, that return types being different is only specific to this concrete implementation of Visitor, in all other cases of IVisitor being implmeted the nodes will all likley have the same return type.
That can't be achieved using the single generic, does anyone have any thoughts on how I can achieve my goal?
Upvotes: 3
Views: 533
Reputation: 38199
You can create an unified return type which will return a type that is necessary.
Let me show an example.
This is a unified return type:
public enum MyType
{
MyString, MyInt, MyClass
}
public class MyTypeValue
{
public MyTypeValue(MyType myType, object value)
{
PropertyType = myType;
Value = value;
}
public MyType PropertyType { get; } // or you can use Type class
// https://learn.microsoft.com/en-us/dotnet/api/system.type?view=net-8.0
public object Value { get; }
}
And then you can put any data you want and how many you want:
public class MyContainer
{
public List<MyTypeValue> MyVariousTypes { get; } = new();
public void Add(MyType myType, object value) =>
MyVariousTypes.Add(new MyTypeValue(myType, value));
}
So your Visitor class would look like this:
public interface IVisitor
{
MyContainer Visit(VisitableClassReturnString visitableClass);
MyContainer Visit(VisitibleClassReturnInt visitibleClass);
}
And implemtation of IVisitor
would look like this:
public class Visitor : IVisitor
{
MyContainer Visit(VisitableClassReturnString visitableClass)
{
MyContainer myContainer= new MyContainer();
myContainer.Add(MyType.MyString, null);
return myContainer;
}
MyContainer Visit(VisitibleClassReturnInt visitibleClass)
{
MyContainer myContainer = new MyContainer();
myContainer.Add(MyType.Int, null);
return myContainer;
}
}
Upvotes: 1
Reputation: 711
In the visitor pattern, all Elements are the same type. you can't change the return type based on the element type. When you visit a collection of elements, you expect that the result of each visit will return the same data type. You can't change the result type in the middle of the elements chain.
Upvotes: 1
Reputation: 233347
That variation of the Visitor design pattern is equivalent to a Church-encoding of a sum type. Thus, visiting an element with a Visitor is equivalent to pattern-matching in a statically typed language like F# or Haskell.
Languages that natively support pattern matching are expression-based, so essentially, you can think of the Visitor as an expression. Like pattern matching, the expression must return a value of a unified type, regardless of which kind of element it visits.
Thus, the TReturn
type in the OP must be the same type returned from all nodes.
What you may choose to do, however, is to return another sum type - e.g. something that can be either a string
or something else.
In C#, an obvious fall-back value might be null, which should work if you declare TReturn
as string?
(nullable string
).
Another option, particularly if you are traversing a tree-like structure, is to return a collection. The nodes that you don't want to handle can then return an empty collection.
Upvotes: 2