Visitor Pattern – operații noi fără a modifica structura obiectelor
În aplicațiile mature, modelele de domeniu tind să fie stabile, dar cerințele de business evoluează constant. Apar nevoi noi: rapoarte, validări, exporturi, calcule sau loguri suplimentare.
Visitor Pattern rezolvă elegant această problemă permițând adăugarea de operații noi peste o structură de obiecte fără a modifica clasele existente.
Este un pattern din categoria Behavioral și este extrem de util atunci când:
-
ai o structură de obiecte complexă (ex: AST, documente, elemente UI, modele de domeniu),
-
vrei să eviți modificarea claselor existente,
-
operațiile aplicate peste obiecte se schimbă mai des decât structura lor.
Problema
Să presupunem că avem o aplicație care lucrează cu diferite tipuri de documente:
-
Invoice
-
Contract
Inițial, vrem să:
-
afișăm informații
-
calculăm valori
-
exportăm date
Dacă adăugăm metode direct în fiecare clasă (Print(), Export(), Validate()), ajungem rapid la:
-
clase suprasolicitate
-
încălcarea Open/Closed Principle
-
cod greu de întreținut
Soluția oferită de Visitor Pattern
Visitor Pattern separă:
-
structura obiectelor (modelele)
-
operațiile aplicate asupra lor
👉 Obiectele acceptă un visitor
👉 Visitor-ul știe ce să facă pentru fiecare tip de obiect
Structura pattern-ului
Componentele principale:
-
Element – interfață comună pentru obiectele vizitate
-
ConcreteElement – implementările reale
-
Visitor – interfață pentru operații
-
ConcreteVisitor – implementări concrete ale operațiilor
-
Object Structure – colecția de elemente
Exemplu concret în C#
1️⃣ Interfața IVisitable
public interface IVisitable
{
void Accept(IVisitor visitor);
}
2️⃣ Interfața IVisitor
public interface IVisitor
{
void VisitInvoice(Invoice invoice);
void VisitContract(Contract contract);
}
3️⃣ Elementele concrete
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️⃣ Visitor concret – afișare date
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️⃣ Visitor concret – export date
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️⃣ Utilizare
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);
}
Avantaje
✅ Respectă Open/Closed Principle
✅ Operațiile sunt centralizate și ușor de extins
✅ Cod mai curat pentru modele
✅ Ideal pentru rapoarte, validări, exporturi, audit
Dezavantaje
⚠️ Dificil de extins structura (adăugarea unui nou element necesită modificarea tuturor visitor-ilor)
⚠️ Pattern mai complex, greu de înțeles la început
⚠️ Nu este ideal pentru structuri simple
Când să folosești Visitor Pattern
✔ Structură de obiecte stabilă
✔ Operații care se schimbă des
✔ Ai nevoie de separare clară între date și logică
✔ Lucrezi cu ierarhii complexe (ex: interpretoare, procesare documente, DDD)
Concluzie
Visitor Pattern este un instrument puternic atunci când vrei să extinzi comportamentul aplicației fără a „atinge” modelele existente.
Folosit corect, poate transforma un cod rigid într-unul extrem de flexibil — exact genul de pattern care își arată valoarea în proiecte mari și pe termen lung.