When we look at patterns, we can find simple ones like the singleton pattern and complicated ones like the Mediator pattern,
and all of them have a lot of explanation, but today we will discuss the visitor pattern,
which is one of the poorly explained patterns but has a lot of benefits in our daily work.
To explain this pattern and how it can Hekp us I’ll use a small and simple example to demonstrate how useful it is.
Assume you have these requirements.
write library that help users on acting with Shapes like Squar and Circle ;this library should be able to calculate the Area for the shapes , but consider when you write your code that may be we want to add new operation later for these shapes ;
👌 this Look simple From the first Look we can do the following
public interface IShape
{
double CalculateArea();
}
public class Square : IShape
{
public double Side { get; init; }
public double CalculateArea()
{
return Side * Side;
}
}
public class Circle : IShape
{
public double Radius { get; init; }
public double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
and we can use it like
public void Main(string[] args)
{
IShape shape = new Square()
{
Side = 16,
};
double area = shape.CalculateArea();
Console.WriteLine($"Area of {shape.GetType().Name} = {area}");
}
How can Visitor Pattern Help You
Image if your manager comes back a few days later and tells you that he wants to add a function that can calculate the perimeter, yes I Know it is getting difficult 🤦♂️, you need to change the interface to add the new function then visit all the concrete classes to add the implementation for this method and it gets complex every time you add new shape, now by using this beautiful and small pattern we can enhance and extend our code without worrying about such new changes. and it will be like this
public interface IShapeVisitor
{
double Visit(Square square);
double Visit(Circle circle);
}
public interface IShape
{
double Accept(IShapeVisitor visitor);
}
public class Square : IShape
{
public double Side { get; init; }
public double Accept(IShapeVisitor visitor)
{
return visitor.Visit(this);
}
}
public class Circle : IShape
{
public double Radius { get; init; }
public double Accept(IShapeVisitor visitor)
{
return visitor.Visit(this);
}
}
public class AreaVisitor : IShapeVisitor
{
public double Visit(Square square)
{
return square.Side * square.Side;
}
public double Visit(Circle circle)
{
return Math.PI * circle.Radius * circle.Radius;
}
}
if you look carfully at the new Implemnation you can find that our concrete classes no longer know how to implement the Area its just accept visitor and the implementation moved to the visitor , then you can use it like this
public void Main(string[] args)
{
IShape shape = new Square()
{
Side = 16,
};
double area = shape.Accept(new AreaVisitor()); //this is where your magic
Console.WriteLine($"The Area of {shape.GetType().Name} equals {area}");
}
Now like Boos you can Add the new Function with just simple step
public class PerimeterVisitor : IShapeVisitor
{
public double Visit(Square square)
{
return 4 * square.Side;
}
public double Visit(Circle circle)
{
return 2 * Math.PI * circle.Radius;
}
}
Operation logic is encapsulated in a single class. Moreover, it also corresponds to Open/Close principle. We don’t edit any code, just add a new one.
Imazing Thing isnt it 😎
What we Learn :
When a visitor is Good :
- Visitor assists in defining a new operation for a class hierarchy without changing them.
- It is beneficial when:The hierarchy of classes is known and is not expected to change, so new operations must be added on a regular basis.And the other way around.
When a visitor is bad:
- New operations must be added.
- The class hierarchy is rarely known and is expected to change.