RO EN

Visitor Pattern – new operations without modifying the structure of objects

Visitor Pattern – new operations without modifying the structure of objects
Doru Bulubasa
26 January 2026

In mature applications, domain models tend to be stable, but business requirements constantly evolve. New needs arise: reports, validations, exports, calculations, or additional logs.

Visitor Pattern elegantly solves this problem by allowing adding new operations over a structure of objects without modifying existing classes.

It is a pattern from the Behavioral category and is extremely useful when:

  • you have a complex object structure (e.g., AST, documents, UI elements, domain models),

  • you want to avoid modifying existing classes,

  • the operations applied over objects change more often than their structure.


Problem

Let's assume we have an application that works with different types of documents:

  • Invoice

  • Contract

Initially, we want to:

  • display information

  • calculate values

  • export data

If we add methods directly into each class (Print(), Export(), Validate()), we quickly end up with:

  • overloaded classes

  • violation of Open/Closed Principle

  • hard-to-maintain code


Solution offered by Visitor Pattern

Visitor Pattern separates:

  • the object structure (the models)

  • the operations applied on them

👉 Objects accept a visitor

👉 The visitor knows what to do for each object type


Pattern structure

Main components:

  1. Element – common interface for visited objects

  2. ConcreteElement – actual implementations

  3. Visitor – interface for operations

  4. ConcreteVisitor – concrete implementations of operations

  5. Object Structure – collection of elements


Concrete example in C#

1️⃣ Interface IVisitable

public interface IVisitable
{
    void Accept(IVisitor visitor);
}


2️⃣ Interface IVisitor

public interface IVisitor
{
    void VisitInvoice(Invoice invoice);
    void VisitContract(Contract contract);
}


3️⃣ Concrete elements

public class Invoice : IVisitable
{
    public string Number { get; set; }
    public decimal Total { get; set; }

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

public class Contract : IVisitable
{
    public string Client { get; set; }
    public DateTime StartDate { get; set; }

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


4️⃣ Concrete visitor – data display

public class PrintVisitor : IVisitor
{
    public void VisitInvoice(Invoice invoice)
    {
        Console.WriteLine($"Invoice {invoice.Number}, Total: {invoice.Total} RON");
    }

    public void VisitContract(Contract contract)
    {
        Console.WriteLine($"Contract with {contract.Client}, Start: {contract.StartDate:d}");
    }
}


5️⃣ Concrete visitor – data export

public class ExportVisitor : IVisitor
{
    public void VisitInvoice(Invoice invoice)
    {
        Console.WriteLine($"Exporting invoice {invoice.Number}...");
    }

    public void VisitContract(Contract contract)
    {
        Console.WriteLine($"Exporting contract for {contract.Client}...");
    }
}


6️⃣ Usage

var documents = new List<IVisitable>
{
    new Invoice { Number = "INV-001", Total = 1200 },
    new Contract { Client = "ACME SRL", StartDate = DateTime.Today }
};

var printVisitor = new PrintVisitor();
var exportVisitor = new ExportVisitor();

foreach (var doc in documents)
{
    doc.Accept(printVisitor);
    doc.Accept(exportVisitor);
}


Advantages

✅ Respects Open/Closed Principle

✅ Operations are centralized and easy to extend

✅ Cleaner code for models

✅ Ideal for reports, validations, exports, audit


Disadvantages

⚠️ Difficult to extend the structure (adding a new element requires modifying all visitors)

⚠️ More complex pattern, hard to understand at first

⚠️ Not ideal for simple structures


When to use Visitor Pattern

✔ Stable object structure

✔ Operations that change frequently

✔ Need for clear separation between data and logic

✔ Working with complex hierarchies (e.g., interpreters, document processing, DDD)


Conclusion

Visitor Pattern is a powerful tool when you want to extend application behavior without "touching" existing models.

Used correctly, it can transform rigid code into extremely flexible code — exactly the kind of pattern that shows its value in large and long-term projects.