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
12 views

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.