0

Every example of the visitor pattern that I see uses abstract classes. One of the major drawbacks of the visitor pattern is the fact that each visitor must implement a .visit method for every acceptor it wants to visit. Another drawback is now every acceptor must implement .accept But why not use interfaces? Does this defeat the purpose of the pattern some how? Why not implement a new visitor that implements a new functionality (a new visit method)? Would this C# example not be considered an implementation of the visitor pattern? If not, why not?

interface IAcceptor
{
    string Name { get; }
    void Accept(IVisitor visitor);
}

interface IVisitor
{
    void Visit(IAcceptor acceptor);
}

class AcceptorA : IAcceptor
{
    public string Name => "ACCEPTOR A";

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

class AcceptorB : IAcceptor
{
    public string Name => "ACCEPTOR B";

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

class VisitorA : IVisitor
{
    public void Visit(IAcceptor acceptor)
    {
        Console.WriteLine($"VISITOR A visiting {acceptor.Name}");
    }
}

class VisitorB : IVisitor
{
    public void Visit(IAcceptor acceptor)
    {
        Console.WriteLine($"VISITOR B visiting {acceptor.Name}");
    }
}
6
  • 5
    You did not tell us where you found your examples, but in the Wikipedia article about this pattern, the first C# example as well as the Java examples use interfaces, not abstract classes. And to your example: this looks far too unspecific to be a meaningful use case for a visitor pattern - meaningless class names like Foo, Bar (or VisitorA) are usually not suitable for making good examples. So structurally, your example looks like a very trivial Visitor implementation, but none which is able to show what this pattern may be good for.
    – Doc Brown
    Sep 10, 2019 at 5:57
  • @DocBrown the example is my own. Ok, that example then. The Visitors implement a method for each Acceptor. Why not use their interface? Sep 10, 2019 at 6:08
  • 1
    @DocBrown, re "meaningless class names like Foo, Bar (or VisitorA) are usually not suitable for making good examples". This is a curious view that seems common here. To my mind, Foo and Bar are the "lingua franca" of example code. Yet many here seem to dislike them. Odd.
    – David Arno
    Sep 10, 2019 at 7:31
  • 4
    @DavidArno: the problem is not really using these names, they are often only a symptom. The real problem is, lots of questions require context for a meaningful, sensible answer, but OP assumes it does not and puts the focus only on code structure without any semantics. Those OP seem to hope for a "braindead rule when to apply a pattern" which can be identified just by syntax/structure, without any design goal. To be fair, if a question is purely about code structure, names like Foo/Bar can be ok. But look into the Wikipedia link above and the kind of examples given there, that is better.
    – Doc Brown
    Sep 10, 2019 at 7:46
  • @DocBrown, OK, that makes sense. Thanks for taking the time to explain.
    – David Arno
    Sep 10, 2019 at 8:20

1 Answer 1

5

Using interfaces to describe the Accept(visitor) method or to describe the required structure of the visitor is perfectly fine. However:

  • the visitor pattern is often used together with a particular class hierarchy, in which the Accept(visitor) method can be included in the hierarchy's base class
  • having the visitor access the visited object through the IAcceptor interface misses the point of this pattern

The problem that the visitor pattern solves is this: I have objects of different types. I want to perform some operation on these objects, depending on their type. One approach would be to add that operation as a method to the objects themselves, so that object.DoTheThing() does the right thing. However, I don't want to add these operations to the classes themselves, but want to keep them separate.

The visitor pattern presents a compromise to the problem: the visitor includes the implementation of these operations for every type, but each object is still responsible for selecting the correct implementation.

As a very simple example, consider that I have animals such as Cat and Dog:

interface IAnimal {}

class Cat: IAnimal {}
class Dog: IAnimal {}

Now I want to add a MakeSound() operation to all animals. One approach is to add the method directly to each class:

interface IAnimal {
  void MakeSound();
}

class Cat: IAnimal {
  public void MakeSound() { Console.WriteLine("meow"); }
}

class Dog: IAnimal {
  public void MakeSound() { Console.WriteLine("woof"); }
}

...
IAnimal animal = ...;
animal.MakeSound();

Adding new methods directly to a class is not always desirable. Perhaps the operation we want to add isn't a core responsibility of those classes. We also cannot add methods to classes if those classes come from another library where we can't edit the source code. By using the Visitor pattern, we can enable future extensions:

interface IAnimal {
  void Accept(IAnimalVisitor visitor);
}

interface IAnimalVisitor {
  void VisitCat(Cat cat);
  void VisitDog(Dog dog);
}

class Cat: IAnimal {
  public void Accept(IAnimalVisitor visitor) {
    visitor.VisitCat(this);
  }
}

class Dog: IAnimal {
  public void Accept(IAnimalVisitor visitor) {
    visitor.VisitDog(this);
  }
}

Now, we can add a MakeSound visitor without having to touch the existing classes:

class MakeSound: IAnimalVisitor {
  public static void Run(IAnimal animal) { animal.Accept(new MakeSound()); }
  public void VisitCat(Cat cat) { Console.WriteLine("meow"); }
  public void VisitDog(Dog dog) { Console.WriteLine("woof"); }
}

IAnimal animal = ...;
MakeSound.Run(animal);
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.