CodeOath
← All posts
Architecture & Patterns80 min total · 26 parts

ACID, SOLID, and Common Design Patterns: A Software Design Reference

Contents — Part 23 of 26: Behavioral Patterns: Visitor
Part 23 of 26 · ~1 min

Behavioral Patterns: Visitor

Visitor lets you add new operations to a family of related classes without modifying those classes themselves — useful when the object structure (a set of shape types, a set of AST node types) is stable, but the operations performed on it keep growing (render, compute area, serialize, validate...).

public interface IShapeVisitor
{
    void Visit(Circle circle);
    void Visit(Square square);
}

public interface IShape
{
    void Accept(IShapeVisitor visitor);
}

public class Circle : IShape
{
    public double Radius { get; set; }
    public void Accept(IShapeVisitor visitor) => visitor.Visit(this); // double-dispatch
}

public class Square : IShape
{
    public double Side { get; set; }
    public void Accept(IShapeVisitor visitor) => visitor.Visit(this);
}

// A new operation — added without touching Circle or Square at all.
public class AreaVisitor : IShapeVisitor
{
    public double TotalArea { get; private set; }
    public void Visit(Circle circle) => TotalArea += Math.PI * circle.Radius * circle.Radius;
    public void Visit(Square square) => TotalArea += square.Side * square.Side;
}

var shapes = new List<IShape> { new Circle { Radius = 2 }, new Square { Side = 3 } };
var areaVisitor = new AreaVisitor();
foreach (var shape in shapes) shape.Accept(areaVisitor);
Console.WriteLine(areaVisitor.TotalArea);

The Accept(this) call is the key mechanic, called double dispatch: it resolves both the concrete shape type and the concrete visitor's matching Visit overload at runtime, which plain method overloading in C# (resolved at compile time, based on the declared type of a variable) can't do on its own for polymorphic collections like the List<IShape> above.