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}");
}
}
1 Answer 1
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);
Explore related questions
See similar questions with these tags.
Foo
andBar
are the "lingua franca" of example code. Yet many here seem to dislike them. Odd.