Ce este
Șablonul Visitor (vizitator) adaugă o operație nouă unei familii de clase fără a le modifica. Clasele învață un singur lucru, o dată — accept($visitor) — și de atunci înainte, fiecare operație nouă este doar un vizitator nou, cu o metodă pentru fiecare clasă.
Inversează compromisul obișnuit: adăugarea operațiilor devine ușoară, iar adăugarea claselor devine modificarea scumpă.
Exemplu: operații peste forme
Formele nu se mai schimbă niciodată. Afișarea ariilor este un vizitator, denumirea lor este altul — iar un vizitator care exportă în SVG, adăugat mâine, nu ar atinge nici Circle, nici Rectangle.
Operații noi, clase neatinse
Ambii vizitatori parcurg aceeași listă de forme prin aceeași ușă accept. Fiecare vizitator ține operația întreagă într-un singur loc, în loc să o împrăștie prin clase.
Ieșire: script PHP
<?php
interface Shape {
public function accept(Visitor $v): void;
}
class Circle implements Shape {
public function __construct(public float $radius) {}
public function accept(Visitor $v): void {
$v->visitCircle($this);
}
}
class Rectangle implements Shape {
public function __construct(
public float $width,
public float $height,
) {}
public function accept(Visitor $v): void {
$v->visitRectangle($this);
}
}
// Un vizitator este o operație întreagă peste fiecare clasă de forme.
interface Visitor {
public function visitCircle(Circle $c): void;
public function visitRectangle(Rectangle $r): void;
}
class AreaPrinter implements Visitor {
public function visitCircle(Circle $c): void {
printf("aria cercului: %.2f\n", 3.14159 * $c->radius * $c->radius);
}
public function visitRectangle(Rectangle $r): void {
printf("aria dreptunghiului: %.2f\n", $r->width * $r->height);
}
}
class NamePrinter implements Visitor {
public function visitCircle(Circle $c): void {
echo "un cerc\n";
}
public function visitRectangle(Rectangle $r): void {
echo "un dreptunghi\n";
}
}
$shapes = [new Circle(2), new Rectangle(3, 4)];
foreach ($shapes as $s) {
$s->accept(new AreaPrinter());
}
foreach ($shapes as $s) {
$s->accept(new NamePrinter());
}
Când să îl folosești
Folosește-l atunci când o familie stabilă de clase primește tot timpul operații noi — compilatoare care vizitează arbori de sintaxă, documente care capătă exportatoare, forme care capătă randare și măsurare.
Renunță la el atunci când familia de clase încă mai crește: fiecare clasă nouă forțează o modificare în fiecare vizitator. Adesea o verificare instanceof sau un match pe $shape::class 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 în câte o clasă; operațiile noi nu cer nicio modificare î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 clase vizitate înseamnă editarea fiecărui vizitator; interfața familiei vizitate este înghețată devreme.