Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User extendable visitor pattern in C#

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);
         }
      }
   }
}
like image 337
Nick Sonneveld Avatar asked Jun 10 '11 05:06

Nick Sonneveld


2 Answers

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).

like image 188
jgauffin Avatar answered Sep 21 '22 09:09

jgauffin


No, it is not possible to mix the visitor pattern with visions of an extensible class hierarchy. They are mutually exclusive.

like image 34
Kirk Woll Avatar answered Sep 24 '22 09:09

Kirk Woll