Ce este
Șablonul Visitor adaugă o operație nouă unei familii de clase fără a le modifica. Clasele învață un singur lucru, o singură dată — accept(visitor) — iar de atunci înainte fiecare operație nouă este doar un vizitator nou, cu câte o metodă pentru fiecare clasă.
Inversează schimbul obișnuit: adăugarea de operații devine ușoară, iar adăugarea de clase devine schimbarea costisitoare.
Exemplu: operații peste forme
Formele nu se mai schimbă niciodată. Afișarea ariilor este un vizitator, denumirea lor este altul — iar un vizitator de export în SVG, mâine, nu ar atinge nici Circle, nici Rectangle.
Operații noi, clase neatinse
Ambii vizitatori parcurg același tablou de forme prin aceeași ușă accept: primul afișează aria cercului: 12.57 și aria dreptunghiului: 12.00, al doilea afișează cele două nume. Fiecare vizitator ține operația întreagă într-un singur loc, în loc să o împrăștie prin clase.
Ieșire: program TypeScript și rezultatul capturat
interface Shape {
accept(visitor: ShapeVisitor): void
}
// Un vizitator este o operație întreagă peste fiecare clasă de formă.
interface ShapeVisitor {
visitCircle(circle: Circle): void
visitRectangle(rectangle: Rectangle): void
}
class Circle implements Shape {
readonly radius: number
constructor(radius: number) {
this.radius = radius
}
accept(visitor: ShapeVisitor): void {
visitor.visitCircle(this)
}
}
class Rectangle implements Shape {
readonly width: number
readonly height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
accept(visitor: ShapeVisitor): void {
visitor.visitRectangle(this)
}
}
class AreaPrinter implements ShapeVisitor {
visitCircle(circle: Circle): void {
console.log("aria cercului:", (Math.PI * circle.radius * circle.radius).toFixed(2))
}
visitRectangle(rectangle: Rectangle): void {
console.log("aria dreptunghiului:", (rectangle.width * rectangle.height).toFixed(2))
}
}
class NamePrinter implements ShapeVisitor {
visitCircle(circle: Circle): void {
console.log("un cerc")
}
visitRectangle(rectangle: Rectangle): void {
console.log("un dreptunghi")
}
}
const shapes: Shape[] = [new Circle(2), new Rectangle(3, 4)]
for (const shape of shapes) {
shape.accept(new AreaPrinter())
}
for (const shape of shapes) {
shape.accept(new NamePrinter())
}
Când să îl folosești
Folosește-l atunci când o familie stabilă de clase primește tot mereu operații noi — compilatoare care vizitează arbori de sintaxă, documente care capătă exportatoare, forme care capătă randatoare și instrumente de măsurare.
Evită-l atunci când familia încă crește: fiecare clasă nouă obligă la o schimbare în fiecare vizitator. În multe programe TypeScript un switch peste o uniune discriminată face aceeași treabă cu mai puțină mașinărie — apelează la vizitator atunci când se înmulțesc operațiile, nu clasele.
Avantaje și dezavantaje
Avantaje: operațiile întregi stau fiecare într-o singură clasă; operațiile noi nu cer nicio schimbare în clasele vizitate; logica înrudită nu mai este împrăștiată.
Dezavantaje: dubla dispecerizare (accept care apelează înapoi) îi încurcă pe începători; adăugarea unei singure clase vizitate înseamnă modificarea fiecărui vizitator; interfața familiei vizitate este înghețată devreme.