Is it possible to create a user extendable visitor pattern in C#? (preferably .net 3.5)
I have a set of classes in a library that I wish to add functionality to with the visitor pattern. The problem is that it is also possible for the user of the library to create their own classes. This means that you need to create a special visitor that will accept the new class types but our Accept methods are setup to receive the base type. How can I get the derived classes to call the right method in the derived visitor.
Or is there another way of doing 'if this type, do this"?
Some example code:
/* In library */
namespace VisitorPattern.System
{
interface IThing
{
void Accept(SystemVisitor visitor);
void ThingMethodA(...);
void ThingMethodB(...);
}
class SystemThingA : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemThingB : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemThingC : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemVisitor
{
public SystemVisitor(object specialSystemServices) { }
public virtual void Visit(SystemThingA thing) { Console.WriteLine("SystemThingA"); }
public virtual void Visit(SystemThingB thing) { Console.WriteLine("SystemThingB"); }
public virtual void Visit(SystemThingC thing) { Console.WriteLine("SystemThingC"); }
public virtual void Visit(IThing thing) { Console.WriteLine("sysvis:IThing"); }
}
}
/* in user code */
namespace VisitorPattern.User
{
using VisitorPattern.System;
class UserThingA : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
class UserThingB : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
class UserThingC : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
// ?????
class UserVisitor : SystemVisitor
{
public UserVisitor(object specialSystemServices, object specialUserServices) : base(specialSystemServices) { }
public void Visit(UserThingA thing) { Console.WriteLine("UserThingA"); }
public void Visit(UserThingB thing) { Console.WriteLine("UserThingB"); }
public void Visit(UserThingC thing) { Console.WriteLine("UserThingC"); }
public override void Visit(IThing thing) { Console.WriteLine("uservis:IThing"); }
}
class Program
{
static void Main(string[] args)
{
var visitor = new UserVisitor("systemservice", "userservice");
List<IThing> mylist = new List<IThing> { new UserThingA(), new SystemThingB(), new SystemThingC(), new UserThingC() };
foreach (var thing in mylist)
{
thing.Accept(visitor);
}
}
}
}
Seems like you got it all backwards. First of all, let's talk about the Liskov Substitution Principle. It says that any type should be replaceable by the base type. This also apply to the visitor pattern.
If you have a method called void Accept(IVisitor visitor)
, it should not matter if a FancyVisitor
or a SipleVisitor
that is visiting.
The whole idea with the visitor pattern is that the subject (i.e. the class that is being visited) should not know anything about the visitor more than the contract (base class or interface) that it implements. And each Visitor
class should be specific for a certain class being visited.
And that's the problem with your code. You are trying to make a general Visitor class that can visit all your system components. That's plain wrong.
As I see it, you have two options:
You want to collect the same kind of information from all system components.
Easy. Create a new interface which all system components implement. Then change the visitor to Visit(ISystemCompoent subject)
.
You want to collect different kinds of information from each system component
Then you need to create different visitor base classes (or interfaces).
No, it is not possible to mix the visitor pattern with visions of an extensible class hierarchy. They are mutually exclusive.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With